@yegor256/dogent 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +104 -12
  2. package/package.json +3 -2
  3. package/src/args.js +35 -4
  4. package/src/defaults.js +47 -0
  5. package/src/dogent.js +42 -16
  6. package/src/openai.js +8 -5
  7. package/src/prompt.js +0 -4
  8. package/src/report.js +8 -2
  9. package/src/rules/ambiguous-or.js +58 -0
  10. package/src/rules/budget.js +50 -0
  11. package/src/rules/concise.js +48 -0
  12. package/src/rules/conditional.js +55 -0
  13. package/src/rules/consistent.js +1 -1
  14. package/src/rules/counter-example.js +60 -0
  15. package/src/rules/default.js +60 -0
  16. package/src/rules/description-length.js +64 -0
  17. package/src/rules/description-voice.js +67 -0
  18. package/src/rules/done.js +53 -0
  19. package/src/rules/duplicate-section.js +65 -0
  20. package/src/rules/emoji.js +60 -0
  21. package/src/rules/emphasis.js +81 -0
  22. package/src/rules/example-format.js +32 -0
  23. package/src/rules/example.js +60 -0
  24. package/src/rules/external-link.js +57 -0
  25. package/src/rules/fence-language.js +55 -0
  26. package/src/rules/format.js +68 -0
  27. package/src/rules/hidden-char.js +61 -0
  28. package/src/rules/homoglyph.js +82 -0
  29. package/src/rules/index.js +80 -0
  30. package/src/rules/inline-code.js +79 -0
  31. package/src/rules/jargon.js +115 -0
  32. package/src/rules/meta-reference.js +57 -0
  33. package/src/rules/ordered.js +57 -0
  34. package/src/rules/persona.js +55 -0
  35. package/src/rules/placement.js +62 -0
  36. package/src/rules/positive.js +57 -0
  37. package/src/rules/pseudo-heading.js +55 -0
  38. package/src/rules/quantifier.js +63 -0
  39. package/src/rules/rationale.js +54 -0
  40. package/src/rules/referential.js +67 -0
  41. package/src/rules/scope.js +31 -0
  42. package/src/rules/self-contained.js +66 -0
  43. package/src/rules/stale.js +62 -0
  44. package/src/rules/terms.js +77 -0
  45. package/src/rules/tool-clarity.js +61 -0
  46. package/src/rules/transition.js +59 -0
  47. package/src/rules/units.js +81 -0
  48. package/src/rules/untrusted.js +59 -0
  49. package/src/rules/vague.js +63 -0
  50. package/src/rules/weak-verb.js +62 -0
  51. package/src/version.js +2 -2
@@ -0,0 +1,55 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * Conditional.
14
+ *
15
+ * Demands that branching never collapse onto one line. A line carrying
16
+ * more than one condition keyword (if, unless, when, else, otherwise)
17
+ * spells out a whole branch tree at once, so each case must split into
18
+ * its own command. Distinct from simple, which weighs clause depth, and
19
+ * from atomic, which counts instructions; this one targets branching
20
+ * alone. A lone guard keeps just one keyword and stays clean.
21
+ */
22
+ class Conditional {
23
+ constructor() {
24
+ this.id = 'conditional';
25
+ }
26
+ prompt() {
27
+ return `${this.id}: flag implicit branching that carries no keyword, and split each case into its own command`;
28
+ }
29
+ violations(document) {
30
+ const uri = document.uri();
31
+ return document.walk({
32
+ header: () => [],
33
+ prose: (text, line) => this.judge(text, line, uri),
34
+ snippet: () => [],
35
+ bullets: () => [],
36
+ frontmatter: () => []
37
+ });
38
+ }
39
+ judge(text, line, uri) {
40
+ const clean = mask(text);
41
+ const hits = clean.match(/\b(?:if|unless|when|else|otherwise)\b/giu);
42
+ if (hits === null || hits.length < 2) {
43
+ return [];
44
+ }
45
+ const column = clean.search(/\b(?:if|unless|when|else|otherwise)\b/iu);
46
+ return [new Violation(
47
+ this.id,
48
+ 'warning',
49
+ 'multi-branch conditional, split each case into its own command',
50
+ new Region(uri, line, column + 1)
51
+ )];
52
+ }
53
+ }
54
+
55
+ module.exports = Conditional;
@@ -20,7 +20,7 @@ class Consistent {
20
20
  this.id = 'consistent';
21
21
  }
22
22
  prompt() {
23
- return `${this.id}: flag an instruction that repeats another instruction word for word, or that directly contradicts another instruction in the same file`;
23
+ return `${this.id}: flag an instruction that repeats another instruction word for word, or that logically contradicts another instruction about the very same subject, where one line orders exactly what another forbids; ignore lines that merely share a theme but govern different concerns, since complementary instructions never clash`;
24
24
  }
25
25
  violations() {
26
26
  return [];
@@ -0,0 +1,60 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * CounterExample.
14
+ *
15
+ * Rejects "bad example" demonstrations that show the wrong form, since
16
+ * displaying a mistake can reinforce it. A standalone checker flags a
17
+ * line that opens a counterexample with an introducer phrase ("bad
18
+ * example", "wrong example", "for example, do not", "instead of
19
+ * writing", "avoid writing") and then carries a quoted or backticked
20
+ * sample of the wrong form. Its prompt hands subtler cases to the AI
21
+ * oracle, which judges whether an example shows the correct or the
22
+ * incorrect behavior.
23
+ */
24
+ class CounterExample {
25
+ constructor() {
26
+ this.id = 'counter-example';
27
+ }
28
+ prompt() {
29
+ return `${this.id}: judge whether each example shows the correct behavior, and flag any example that demonstrates the incorrect form`;
30
+ }
31
+ violations(document) {
32
+ const uri = document.uri();
33
+ return document.walk({
34
+ header: () => [],
35
+ prose: (text, line) => this.scan(text, line, uri),
36
+ snippet: () => [],
37
+ bullets: () => [],
38
+ frontmatter: () => []
39
+ });
40
+ }
41
+ scan(text, line, uri) {
42
+ const regex = /bad example|wrong example|for example, do not|instead of writing|avoid writing/iu;
43
+ const hit = regex.exec(mask(text));
44
+ if (hit === null) {
45
+ return [];
46
+ }
47
+ const tail = text.slice(hit.index + hit[0].length);
48
+ if (!/["'`]/u.test(tail)) {
49
+ return [];
50
+ }
51
+ return [new Violation(
52
+ this.id,
53
+ 'warning',
54
+ 'counterexample may reinforce the wrong behavior, show the right form',
55
+ new Region(uri, line, hit.index + 1)
56
+ )];
57
+ }
58
+ }
59
+
60
+ module.exports = CounterExample;
@@ -0,0 +1,60 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * Default.
14
+ *
15
+ * Demands that optional behavior names its default. A line marking work
16
+ * as optional through "optionally", "you may", or "feel free to" leaves
17
+ * the agent guessing what happens when it declines, so the line must
18
+ * state a default. A line that already declares one through "by
19
+ * default", "defaults to", or "otherwise" passes untouched. Its prompt
20
+ * hands subtler optionality with no stated default to the AI oracle.
21
+ */
22
+ class Default {
23
+ constructor() {
24
+ this.id = 'default';
25
+ }
26
+ prompt() {
27
+ return `${this.id}: flag optionality that names no default even without a listed marker, and state the default`;
28
+ }
29
+ violations(document) {
30
+ const uri = document.uri();
31
+ return document.walk({
32
+ header: () => [],
33
+ prose: (text, line) => this.scan(text, line, uri),
34
+ snippet: () => [],
35
+ bullets: () => [],
36
+ frontmatter: () => []
37
+ });
38
+ }
39
+ scan(text, line, uri) {
40
+ const masked = mask(text);
41
+ if ((/\b(?:by default|default to|defaults to|otherwise)\b/iu).test(masked)) {
42
+ return [];
43
+ }
44
+ const found = [];
45
+ const regex = /\b(?:optionally|you may|you can|if you want|feel free to|as an option)\b/giu;
46
+ let hit = regex.exec(masked);
47
+ while (hit !== null) {
48
+ found.push(new Violation(
49
+ this.id,
50
+ 'warning',
51
+ `optional behavior "${hit[0]}" has no default, state it`,
52
+ new Region(uri, line, hit.index + 1)
53
+ ));
54
+ hit = regex.exec(masked);
55
+ }
56
+ return found;
57
+ }
58
+ }
59
+
60
+ module.exports = Default;
@@ -0,0 +1,64 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+
11
+ /**
12
+ * DescriptionLength.
13
+ *
14
+ * Demands that a SKILL.md description stay within a sane size. The
15
+ * loader keeps every description in context, so an overgrown one wastes
16
+ * the budget that the instructions need. Flags a value longer than the
17
+ * ceiling and a value that is empty, leaving the wording itself to
18
+ * sibling rules.
19
+ *
20
+ * The check is standalone and deterministic, so prompt() returns an
21
+ * empty string and the AI oracle never re-checks this rule.
22
+ */
23
+ class DescriptionLength {
24
+ constructor() {
25
+ this.id = 'description-length';
26
+ this.ceiling = 1024;
27
+ }
28
+ prompt() {
29
+ return '';
30
+ }
31
+ violations(document) {
32
+ const uri = document.uri();
33
+ if (uri.replace(/^.*\//u, '') !== 'SKILL.md') {
34
+ return [];
35
+ }
36
+ const pairs = document.walk({
37
+ header: () => [],
38
+ prose: () => [],
39
+ snippet: () => [],
40
+ bullets: () => [],
41
+ frontmatter: (keys) => keys
42
+ });
43
+ const found = pairs.filter((pair) => pair.key === 'description');
44
+ if (found.length === 0) {
45
+ return [];
46
+ }
47
+ return this.judge(found[0], uri);
48
+ }
49
+ judge(pair, uri) {
50
+ const {value} = pair;
51
+ if (value.trim() === '') {
52
+ return [this.flag('description is empty, write a concise capability statement', pair.row, uri)];
53
+ }
54
+ if (value.length > this.ceiling) {
55
+ return [this.flag(`description is ${value.length} chars, keep it under ${this.ceiling}`, pair.row, uri)];
56
+ }
57
+ return [];
58
+ }
59
+ flag(message, row, uri) {
60
+ return new Violation(this.id, 'warning', message, new Region(uri, row, 1));
61
+ }
62
+ }
63
+
64
+ module.exports = DescriptionLength;
@@ -0,0 +1,67 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+
11
+ /**
12
+ * DescriptionVoice.
13
+ *
14
+ * Demands that a SKILL.md description stay in the third person, reading
15
+ * as a capability statement like "Extracts tables ..." rather than a
16
+ * first- or second-person sentence like "I extract ..." or "You can
17
+ * use ...". A standalone checker flags first- and second-person
18
+ * pronouns as whole words, after dropping the trigger clause that opens
19
+ * with "Use when" so a legitimate "Use when ..." phrase stays clean.
20
+ * Distinct from description-triggers, which checks that a "when" clause
21
+ * exists, and from description-length, which checks the size; this one
22
+ * checks the grammatical voice. Its prompt hands subtler voice
23
+ * judgement to the AI oracle.
24
+ */
25
+ class DescriptionVoice {
26
+ constructor() {
27
+ this.id = 'description-voice';
28
+ this.pronoun = /\b(?:I|we|you|your|my|our)\b/giu;
29
+ }
30
+ prompt() {
31
+ return `${this.id}: in a SKILL.md, flag a description written in first or second person and demand a third-person capability statement`;
32
+ }
33
+ violations(document) {
34
+ const uri = document.uri();
35
+ if (uri.replace(/^.*\//u, '') !== 'SKILL.md') {
36
+ return [];
37
+ }
38
+ const pairs = document.walk({
39
+ header: () => [],
40
+ prose: () => [],
41
+ snippet: () => [],
42
+ bullets: () => [],
43
+ frontmatter: (keys) => keys
44
+ });
45
+ const found = pairs.filter((pair) => pair.key === 'description');
46
+ if (found.length === 0) {
47
+ return [];
48
+ }
49
+ return this.judge(found[0], uri);
50
+ }
51
+ judge(pair, uri) {
52
+ const text = pair.value.replace(/use when.*$/isu, '');
53
+ this.pronoun.lastIndex = 0;
54
+ const hit = this.pronoun.exec(text);
55
+ if (hit === null) {
56
+ return [];
57
+ }
58
+ return [new Violation(
59
+ this.id,
60
+ 'warning',
61
+ `description must be third person, not "${hit[0]}"`,
62
+ new Region(uri, pair.row, 1)
63
+ )];
64
+ }
65
+ }
66
+
67
+ module.exports = DescriptionVoice;
@@ -0,0 +1,53 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+
11
+ /**
12
+ * Done.
13
+ *
14
+ * Demands that a SKILL.md state a verifiable completion check, symmetric
15
+ * to the description trigger requirement. A standalone checker can only
16
+ * approximate: it scans headings and prose for a verification signal. Its
17
+ * prompt hands the deeper judgement to the AI oracle, which weighs whether
18
+ * the stated check is truly pass/fail testable rather than vague.
19
+ */
20
+ class Done {
21
+ constructor() {
22
+ this.id = 'done';
23
+ }
24
+ prompt() {
25
+ return `${this.id}: in a SKILL.md, judge whether the stated completion check is actually pass/fail testable rather than a vague gesture toward being finished`;
26
+ }
27
+ violations(document) {
28
+ const uri = document.uri();
29
+ if (uri.replace(/^.*\//u, '') !== 'SKILL.md') {
30
+ return [];
31
+ }
32
+ const signals = document.walk({
33
+ header: (text) => [/\b(?:verify|done|check|validation|acceptance)\b/iu.test(text)],
34
+ prose: (text) => [/\b(?:confirm|assert|verify|the test passes|tests pass|exit code|pass\/fail)\b/iu.test(text)],
35
+ snippet: () => [],
36
+ bullets: () => [],
37
+ frontmatter: () => []
38
+ });
39
+ if (signals.some((signal) => signal)) {
40
+ return [];
41
+ }
42
+ return [
43
+ new Violation(
44
+ this.id,
45
+ 'warning',
46
+ 'SKILL.md never says how to verify completion',
47
+ new Region(uri, 1, 1)
48
+ )
49
+ ];
50
+ }
51
+ }
52
+
53
+ module.exports = Done;
@@ -0,0 +1,65 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+
11
+ const bare = (text) => text.replace(/^#{1,6}\s*/u, '').trim();
12
+
13
+ const normalize = (text) => bare(text).toLowerCase().replace(/\s+/gu, ' ');
14
+
15
+ /**
16
+ * DuplicateSection.
17
+ *
18
+ * Rejects two headings that carry the same name, so each section owns
19
+ * a distinct title. It collects every heading in order, normalizes it
20
+ * by case and whitespace, then flags the second and any later twin
21
+ * while leaving the first occurrence clean. Distinct from unique,
22
+ * which targets repeated prose instructions, and from short-sections,
23
+ * which targets heading length; this one targets repeated heading
24
+ * names. Its prompt stays empty since the check is fully
25
+ * deterministic.
26
+ */
27
+ class DuplicateSection {
28
+ constructor() {
29
+ this.id = 'duplicate-section';
30
+ }
31
+ prompt() {
32
+ return '';
33
+ }
34
+ violations(document) {
35
+ const uri = document.uri();
36
+ const headers = document.walk({
37
+ header: (text, row) => [{text, row}],
38
+ prose: () => [],
39
+ snippet: () => [],
40
+ bullets: () => [],
41
+ frontmatter: () => []
42
+ });
43
+ return this.repeats(uri, headers);
44
+ }
45
+ repeats(uri, headers) {
46
+ const seen = new Set();
47
+ const found = [];
48
+ headers.forEach((header) => {
49
+ const norm = normalize(header.text);
50
+ if (seen.has(norm)) {
51
+ found.push(new Violation(
52
+ this.id,
53
+ 'warning',
54
+ `duplicate section "${bare(header.text)}", give each section a distinct name`,
55
+ new Region(uri, header.row, 1)
56
+ ));
57
+ } else {
58
+ seen.add(norm);
59
+ }
60
+ });
61
+ return found;
62
+ }
63
+ }
64
+
65
+ module.exports = DuplicateSection;
@@ -0,0 +1,60 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * Emoji.
14
+ *
15
+ * Flags any emoji or decorative pictographic symbol that adds token
16
+ * noise without instruction. Inline code is masked first, so a fenced
17
+ * or inline example may keep a needed glyph. Distinct from homoglyph,
18
+ * which targets letters borrowed from other scripts; this one stays to
19
+ * pictographs, symbols, and dingbats only and never flags a foreign
20
+ * letter.
21
+ *
22
+ * The check is standalone and deterministic, so prompt() returns an
23
+ * empty string and the AI oracle never re-checks this rule.
24
+ */
25
+ class Emoji {
26
+ constructor() {
27
+ this.id = 'emoji';
28
+ this.glyph = /[\p{Extended_Pictographic}\u{2190}-\u{21FF}\u{2300}-\u{27BF}\u{2B00}-\u{2BFF}]/gu;
29
+ }
30
+ prompt() {
31
+ return '';
32
+ }
33
+ violations(document) {
34
+ const uri = document.uri();
35
+ return document.walk({
36
+ header: (text, line) => this.scan(text, line, uri),
37
+ prose: (text, line) => this.scan(text, line, uri),
38
+ snippet: () => [],
39
+ bullets: () => [],
40
+ frontmatter: () => []
41
+ });
42
+ }
43
+ scan(text, line, uri) {
44
+ const masked = mask(text);
45
+ const result = [];
46
+ let hit = this.glyph.exec(masked);
47
+ while (hit !== null) {
48
+ result.push(new Violation(
49
+ this.id,
50
+ 'warning',
51
+ `decorative character "${hit[0]}" adds token noise, use plain text`,
52
+ new Region(uri, line, hit.index + 1)
53
+ ));
54
+ hit = this.glyph.exec(masked);
55
+ }
56
+ return result;
57
+ }
58
+ }
59
+
60
+ module.exports = Emoji;
@@ -0,0 +1,81 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * Emphasis.
14
+ *
15
+ * Flags shouting that tries to force compliance through volume rather
16
+ * than clarity: a curated all-caps word like "IMPORTANT" or "NEVER", a
17
+ * run of two or more consecutive all-caps words, and repeated marks like
18
+ * "!!" or "!?". The model gains nothing from volume, so the emphasis is
19
+ * pure noise. A lone short acronym such as "JSON" or "AI" is left alone.
20
+ * Its prompt hands the borderline emphasis and reward framing the
21
+ * patterns miss to the AI oracle.
22
+ */
23
+ class Emphasis {
24
+ constructor() {
25
+ this.id = 'emphasis';
26
+ this.shout = new Set(['IMPORTANT', 'ALWAYS', 'NEVER', 'MUST', 'CRITICAL', 'REQUIRED']);
27
+ }
28
+ prompt() {
29
+ return `${this.id}: flag emphatic shouting the patterns miss, including borderline all-caps and reward framing, since emphasis adds no instruction`;
30
+ }
31
+ violations(document) {
32
+ const uri = document.uri();
33
+ return document.walk({
34
+ header: () => [],
35
+ prose: (text, line) => this.scan(text, line, uri),
36
+ snippet: () => [],
37
+ bullets: () => [],
38
+ frontmatter: () => []
39
+ });
40
+ }
41
+ scan(text, line, uri) {
42
+ const masked = mask(text);
43
+ return this.punctuation(masked, line, uri).concat(this.shouting(masked, line, uri));
44
+ }
45
+ punctuation(masked, line, uri) {
46
+ const found = [];
47
+ const regex = /!{2,}|!\?|\?!/gu;
48
+ let hit = regex.exec(masked);
49
+ while (hit !== null) {
50
+ found.push(this.flag(hit[0], line, hit.index, uri));
51
+ hit = regex.exec(masked);
52
+ }
53
+ return found;
54
+ }
55
+ shouting(masked, line, uri) {
56
+ const found = [];
57
+ const regex = /[A-Z]{2,}(?:\s+[A-Z]{2,})*/gu;
58
+ let hit = regex.exec(masked);
59
+ while (hit !== null) {
60
+ const tokens = hit[0].split(/\s+/u);
61
+ const loud = tokens.length > 1
62
+ ? tokens.some((token) => token.length >= 5 || this.shout.has(token))
63
+ : this.shout.has(tokens[0]);
64
+ if (loud) {
65
+ found.push(this.flag(hit[0], line, hit.index, uri));
66
+ }
67
+ hit = regex.exec(masked);
68
+ }
69
+ return found;
70
+ }
71
+ flag(marker, line, index, uri) {
72
+ return new Violation(
73
+ this.id,
74
+ 'warning',
75
+ `emphasis marker "${marker}" adds no instruction, state it plainly`,
76
+ new Region(uri, line, index + 1)
77
+ );
78
+ }
79
+ }
80
+
81
+ module.exports = Emphasis;
@@ -0,0 +1,32 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Example format.
10
+ *
11
+ * A few-shot demonstration regulates the shape of the output more
12
+ * strongly than any prose, so an example that disagrees with the
13
+ * declared format teaches the agent the wrong shape. This rule ties the
14
+ * `example` and `format` rules together by checking their consistency:
15
+ * when one SKILL.md both shows an example and declares an output format,
16
+ * the two must agree. The mismatch hides between two distant fragments,
17
+ * so this check is pure judgement: prompt() hands the comparison to the
18
+ * AI oracle and violations() finds nothing on its own.
19
+ */
20
+ class ExampleFormat {
21
+ constructor() {
22
+ this.id = 'example-format';
23
+ }
24
+ prompt() {
25
+ return `${this.id}: in a SKILL.md that both shows an example and declares an output format, judge whether the example conforms to the declared format and flag any mismatch`;
26
+ }
27
+ violations() {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ module.exports = ExampleFormat;
@@ -0,0 +1,60 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Violation = require('../violation');
9
+ const Region = require('../region');
10
+
11
+ /**
12
+ * Example.
13
+ *
14
+ * Demands that a SKILL.md demonstrate, not only describe. A skill that
15
+ * states rules in prose alone leaves the agent to infer the exact shape
16
+ * of correct output, while a single worked example is one of the most
17
+ * reliable levers in prompt engineering. A standalone checker passes the
18
+ * skill that carries at least one fenced code block or an explicit
19
+ * "Example" section heading, and flags the one that has neither. Its
20
+ * prompt hands the deeper judgement to the AI oracle, which weighs
21
+ * whether a present code block is truly illustrative.
22
+ */
23
+ class Example {
24
+ constructor() {
25
+ this.id = 'example';
26
+ }
27
+ prompt() {
28
+ return `${this.id}: in a SKILL.md, judge whether a present code block is a genuine worked example rather than a stray snippet, and flag a skill that only describes without demonstrating`;
29
+ }
30
+ violations(document) {
31
+ const uri = document.uri();
32
+ if (uri.replace(/^.*\//u, '') !== 'SKILL.md') {
33
+ return [];
34
+ }
35
+ const hints = document.walk({
36
+ header: (text) => this.heading(text),
37
+ prose: () => [],
38
+ snippet: () => ['snippet'],
39
+ bullets: () => [],
40
+ frontmatter: () => []
41
+ });
42
+ if (hints.length > 0) {
43
+ return [];
44
+ }
45
+ return [new Violation(
46
+ this.id,
47
+ 'warning',
48
+ 'SKILL.md has no example, add a worked input/output sample',
49
+ new Region(uri, 1, 1)
50
+ )];
51
+ }
52
+ heading(text) {
53
+ if (/^#{1,6}\s+examples?\b/iu.test(text)) {
54
+ return [this.id];
55
+ }
56
+ return [];
57
+ }
58
+ }
59
+
60
+ module.exports = Example;