@yegor256/dogent 0.11.0 → 0.12.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.
Files changed (72) hide show
  1. package/README.md +50 -5
  2. package/package.json +1 -1
  3. package/src/args.js +6 -1
  4. package/src/defaults.js +6 -1
  5. package/src/dogent.js +46 -15
  6. package/src/report.js +26 -3
  7. package/src/rules/ambiguous-or.js +3 -0
  8. package/src/rules/atomic.js +3 -0
  9. package/src/rules/budget.js +3 -0
  10. package/src/rules/command.js +3 -0
  11. package/src/rules/concise.js +3 -0
  12. package/src/rules/conditional.js +3 -0
  13. package/src/rules/consistent.js +3 -0
  14. package/src/rules/counter-example.js +3 -0
  15. package/src/rules/crowded.js +3 -0
  16. package/src/rules/dead-import.js +4 -1
  17. package/src/rules/default.js +3 -0
  18. package/src/rules/description-length.js +3 -0
  19. package/src/rules/description-triggers.js +3 -0
  20. package/src/rules/description-voice.js +3 -0
  21. package/src/rules/done.js +3 -0
  22. package/src/rules/duplicate-section.js +3 -0
  23. package/src/rules/emoji.js +3 -0
  24. package/src/rules/emphasis.js +3 -0
  25. package/src/rules/empty.js +3 -0
  26. package/src/rules/example-format.js +3 -0
  27. package/src/rules/example.js +3 -0
  28. package/src/rules/external-link.js +3 -0
  29. package/src/rules/fence-language.js +3 -0
  30. package/src/rules/format.js +3 -0
  31. package/src/rules/frontmatter.js +3 -0
  32. package/src/rules/grouped.js +3 -0
  33. package/src/rules/hedging.js +3 -0
  34. package/src/rules/hidden-char.js +3 -0
  35. package/src/rules/homoglyph.js +3 -0
  36. package/src/rules/inline-code.js +3 -0
  37. package/src/rules/jargon.js +9 -3
  38. package/src/rules/line-length.js +3 -0
  39. package/src/rules/meta-reference.js +3 -0
  40. package/src/rules/name-format.js +3 -0
  41. package/src/rules/name-matches-dir.js +3 -0
  42. package/src/rules/no-articles.js +3 -0
  43. package/src/rules/ordered.js +3 -0
  44. package/src/rules/passive.js +3 -0
  45. package/src/rules/persona.js +3 -0
  46. package/src/rules/placement.js +3 -0
  47. package/src/rules/polite.js +3 -0
  48. package/src/rules/positive.js +6 -2
  49. package/src/rules/pseudo-heading.js +3 -0
  50. package/src/rules/punctuation.js +3 -0
  51. package/src/rules/quantifier.js +3 -0
  52. package/src/rules/rationale.js +3 -0
  53. package/src/rules/redundant.js +3 -0
  54. package/src/rules/referential.js +3 -0
  55. package/src/rules/scope.js +3 -0
  56. package/src/rules/section-level.js +3 -0
  57. package/src/rules/self-contained.js +3 -0
  58. package/src/rules/short-sections.js +3 -0
  59. package/src/rules/simple.js +3 -0
  60. package/src/rules/stale.js +3 -0
  61. package/src/rules/terms.js +3 -0
  62. package/src/rules/token-count.js +3 -0
  63. package/src/rules/tool-clarity.js +3 -0
  64. package/src/rules/transition.js +3 -0
  65. package/src/rules/unfinished.js +3 -0
  66. package/src/rules/unique.js +3 -0
  67. package/src/rules/units.js +3 -0
  68. package/src/rules/untrusted.js +3 -0
  69. package/src/rules/vague.js +3 -0
  70. package/src/rules/weak-verb.js +3 -0
  71. package/src/sources.js +3 -0
  72. package/src/version.js +2 -2
package/README.md CHANGED
@@ -53,7 +53,7 @@ Most rewrite prompts for you or score a file, while we enforce
53
53
  Run it on any manifesto file, no installation required:
54
54
 
55
55
  ```bash
56
- npx @yegor256/dogent@0.10.0 SKILL.md
56
+ npx @yegor256/dogent@0.11.0 SKILL.md
57
57
  ```
58
58
 
59
59
  Point it at a directory to lint the default manifestos it holds
@@ -75,9 +75,14 @@ CLAUDE.md
75
75
  24: article "the" detected, remove noise
76
76
  31: section name too long, use 1-3 words
77
77
 
78
- 4 problems found, exit code 1
78
+ Locally: 4 problems found, exit code 1
79
+ Spotted a false positive? dogent is in beta, please report it at https://github.com/yegor256/dogent/issues
79
80
  ```
80
81
 
82
+ The standalone checks report under their own `Locally:` summary line.
83
+ When a token enables AI verification, a second `OpenAI:` summary line
84
+ follows, reporting the problems the model found on top.
85
+
81
86
  The command exits with a non-zero status when problems are found,
82
87
  so it plugs directly into CI and pre-commit hooks.
83
88
 
@@ -124,11 +129,13 @@ The command exits with a non-zero status when problems are found,
124
129
  `dogent` works standalone by default,
125
130
  using fast deterministic checks with no network access.
126
131
  When `OPENAI_API_KEY` is present in the environment,
127
- and only after the standalone rules find nothing,
128
- `dogent` asks OpenAI for a second, deeper opinion.
132
+ `dogent` first reports the problems found locally,
133
+ then asks OpenAI for a second, deeper opinion and reports those apart.
129
134
  It sends the manifesto together with one instruction per rule,
130
135
  then prints any violation the model reports for ambiguity,
131
136
  weak phrasing, and instructions that only pretend to be commands.
137
+ Each step closes with its own summary line, `Locally:` then `OpenAI:`,
138
+ so both counts stay visible even when neither step finds a problem.
132
139
  The model defaults to `gpt-4o-mini`; override it with `OPENAI_MODEL`.
133
140
  Requests go to `https://api.openai.com/v1` by default;
134
141
  set `OPENAI_BASE_URL` to any OpenAI-compatible endpoint instead,
@@ -162,6 +169,14 @@ npx @yegor256/dogent --offline CLAUDE.md
162
169
 
163
170
  Pass `--sarif` to print the report as SARIF instead of plain text.
164
171
 
172
+ Pass `--hints` to append, for every rule that reported a violation, one
173
+ English paragraph explaining how to fix it. This helps you or your agent
174
+ repair the manifesto faster:
175
+
176
+ ```bash
177
+ npx @yegor256/dogent --hints CLAUDE.md
178
+ ```
179
+
165
180
  Pass `--suppress` to silence a rule by its id. Repeat the option or
166
181
  join several ids with commas to silence many at once:
167
182
 
@@ -251,7 +266,7 @@ Reference `dogent` as a remote hook in `.pre-commit-config.yaml`:
251
266
  ```yaml
252
267
  repos:
253
268
  - repo: https://github.com/yegor256/dogent
254
- rev: 0.10.0
269
+ rev: 0.11.0
255
270
  hooks:
256
271
  - id: dogent
257
272
  ```
@@ -271,3 +286,33 @@ repos:
271
286
  ```
272
287
 
273
288
  Either way, the commit is rejected until every flagged line is fixed.
289
+
290
+ ## Architecture
291
+
292
+ The component diagram below shows how the pieces fit together
293
+ ([source](components.svg)):
294
+
295
+ ![dogent component diagram](components.svg)
296
+
297
+ A user, or an AI agent, runs `dogent` from the console against a manifesto
298
+ such as `SKILL.md`.
299
+ `dogent` parses the file into a document, applies every deterministic rule,
300
+ and renders the violations as text or SARIF.
301
+ Only after the rules find nothing, and only when a token is present,
302
+ it asks an OpenAI-compatible LLM for a second, deeper opinion.
303
+
304
+ ## How to Contribute
305
+
306
+ First, make sure you can build it locally:
307
+
308
+ ```bash
309
+ npm test
310
+ ```
311
+
312
+ The build has to be clean.
313
+ If it's not, [submit an issue](https://github.com/yegor256/dogent/issues).
314
+
315
+ Then, make your changes, make sure the build is still clean,
316
+ and [submit a pull request][guidelines].
317
+
318
+ [guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html
package/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "lint": "eslint .",
41
41
  "test": "mocha 'test/**/*.js' --timeout 60000"
42
42
  },
43
- "version": "0.11.0",
43
+ "version": "0.12.1",
44
44
  "dependencies": {
45
45
  "minimist": "^1.2.8",
46
46
  "pretty-ms": "^7.0.1"
package/src/args.js CHANGED
@@ -17,6 +17,8 @@ const minimist = require('minimist');
17
17
  * flag, also spelled `-h`, asks for the usage banner. The `--version` flag
18
18
  * asks for the release number. The `--suppress` option names a rule to
19
19
  * silence; repeat it or join names with commas to silence many at once. The
20
+ * `--hints` flag appends, for every rule that reported a violation, one
21
+ * English paragraph telling the agent how to fix it. The
20
22
  * `--openai-http-header` option adds one `Name: Value` header to every OpenAI
21
23
  * call; repeat it to add many. Everything after a `--` separator counts as a
22
24
  * path, never as an option.
@@ -24,7 +26,7 @@ const minimist = require('minimist');
24
26
  class Args {
25
27
  constructor(
26
28
  argv,
27
- flags = ['sarif', 'offline', 'help', 'version'],
29
+ flags = ['sarif', 'offline', 'help', 'version', 'hints'],
28
30
  options = ['suppress', 'openai-http-header']
29
31
  ) {
30
32
  this.flags = flags;
@@ -46,6 +48,9 @@ class Args {
46
48
  version() {
47
49
  return this.parsed.version === true;
48
50
  }
51
+ hints() {
52
+ return this.parsed.hints === true;
53
+ }
49
54
  suppress() {
50
55
  return [].concat(this.parsed.suppress || [])
51
56
  .flatMap((item) => String(item).split(','))
package/src/defaults.js CHANGED
@@ -40,7 +40,12 @@ class Defaults {
40
40
  .split('\n')
41
41
  .map((line) => line.trim())
42
42
  .filter((line) => line !== '' && !line.startsWith('#'))
43
- .flatMap((line) => line.split(/\s+/u));
43
+ .flatMap((line) => {
44
+ const separator = line.search(/\s/u);
45
+ return separator < 0
46
+ ? [line]
47
+ : [line.slice(0, separator), line.slice(separator).trimStart()];
48
+ });
44
49
  }
45
50
  }
46
51
 
package/src/dogent.js CHANGED
@@ -21,7 +21,7 @@ const rules = require('./rules');
21
21
 
22
22
  const args = new Args(new Defaults().argv().concat(process.argv.slice(2)));
23
23
  const sarif = args.sarif();
24
- const banner = 'Usage: dogent [--sarif] [--offline] [--suppress=RULE,...] <file.md|dir>...';
24
+ const banner = 'Usage: dogent [--sarif] [--offline] [--hints] [--suppress=RULE,...] <file.md|dir>...';
25
25
  if (args.version()) {
26
26
  process.stdout.write(`${version}\n`);
27
27
  process.exit(0);
@@ -34,6 +34,7 @@ if (args.help()) {
34
34
  ' --sarif render the report as SARIF JSON\n' +
35
35
  ' --offline never call the LLM, even when a token exists\n' +
36
36
  ' --suppress silence a rule by id; repeat or comma-join to silence many\n' +
37
+ ' --hints append a fixing hint for every rule that reported a violation\n' +
37
38
  ' --openai-http-header add a "Name: Value" header to OpenAI calls\n' +
38
39
  ' --version show the version and exit\n' +
39
40
  ' --help show this help and exit\n\n' +
@@ -61,7 +62,17 @@ if (paths.length === 0) {
61
62
  process.stderr.write(`${banner}\n`);
62
63
  process.exit(2);
63
64
  }
64
- const scanned = new Sources(paths).files();
65
+ const scan = () => {
66
+ try {
67
+ return new Sources(paths).files();
68
+ } catch (error) {
69
+ process.stderr.write(`${error.message}\n`);
70
+ process.stderr.write(`${banner}\n`);
71
+ process.exit(2);
72
+ }
73
+ return [];
74
+ };
75
+ const scanned = scan();
65
76
  scanned.forEach((file) => process.stderr.write(`Scanning ${file}\n`));
66
77
  const checks = rules();
67
78
  process.stderr.write(`${scanned.length} files scanned, ${checks.length} rules applied\n`);
@@ -77,6 +88,7 @@ documents.forEach((document) => {
77
88
  rule.violations(document).filter(allowed).forEach((violation) => found.push(violation));
78
89
  });
79
90
  });
91
+ const localMillis = Date.now() - started;
80
92
  const key = process.env.OPENAI_API_KEY;
81
93
  const audit = async (docs) => {
82
94
  const oracle = new Oracle(
@@ -100,23 +112,42 @@ const audit = async (docs) => {
100
112
  {extra: [], usage: new Usage('', 0, 0)}
101
113
  );
102
114
  };
103
- const finish = (usage, aiMillis) => {
104
- const report = new Report('dogent', found, Date.now() - started);
105
- process.stdout.write(`${sarif ? JSON.stringify(report.sarif(), null, 2) : report.text()}\n`);
106
- if (usage !== null) {
107
- process.stderr.write(`${usage.text()}, analysed in ${prettyMs(aiMillis)}\n`);
108
- }
109
- process.exit(report.count() > 0 ? 1 : 0);
110
- };
111
115
  const verify = async () => {
112
116
  const clock = Date.now();
113
117
  const result = await audit(documents);
114
- result.extra.filter(allowed).forEach((violation) => found.push(violation));
115
- return {usage: result.usage, aiMillis: Date.now() - clock};
118
+ return {
119
+ extra: result.extra.filter(allowed),
120
+ usage: result.usage,
121
+ aiMillis: Date.now() - clock
122
+ };
123
+ };
124
+ const consult = Boolean(key) && !args.offline();
125
+ const human = (outcome) => {
126
+ const all = found.concat(outcome.extra);
127
+ process.stdout.write(`${new Report('dogent', found, localMillis, 'Locally').text()}\n`);
128
+ if (consult) {
129
+ process.stdout.write(`${new Report('dogent', outcome.extra, outcome.aiMillis, 'OpenAI').text()}\n`);
130
+ }
131
+ if (args.hints() && all.length > 0) {
132
+ process.stdout.write(`\n${new Report('dogent', all).hints(checks)}\n`);
133
+ }
134
+ };
135
+ const render = (outcome) => {
136
+ const all = found.concat(outcome.extra);
137
+ if (sarif) {
138
+ const report = new Report('dogent', all, localMillis + outcome.aiMillis);
139
+ process.stdout.write(`${JSON.stringify(report.sarif(), null, 2)}\n`);
140
+ } else {
141
+ human(outcome);
142
+ }
143
+ if (outcome.usage !== null) {
144
+ process.stderr.write(`${outcome.usage.text()}, analysed in ${prettyMs(outcome.aiMillis)}\n`);
145
+ }
146
+ process.exit(all.length > 0 ? 1 : 0);
116
147
  };
117
148
  (async () => {
118
- let outcome = {aiMillis: 0, usage: null};
119
- if (found.length === 0 && key && !args.offline()) {
149
+ let outcome = {extra: [], aiMillis: 0, usage: null};
150
+ if (consult) {
120
151
  try {
121
152
  outcome = await verify();
122
153
  } catch (error) {
@@ -124,5 +155,5 @@ const verify = async () => {
124
155
  process.exit(2);
125
156
  }
126
157
  }
127
- finish(outcome.usage, outcome.aiMillis);
158
+ render(outcome);
128
159
  })();
package/src/report.js CHANGED
@@ -14,21 +14,44 @@ const prettyMs = require('pretty-ms');
14
14
  * violation it gathered. Renders itself for humans or as a SARIF log.
15
15
  * When handed the analysis duration in milliseconds, the human text
16
16
  * closes with a friendly "in 340ms" rendered through pretty-ms.
17
+ * A label tags the summary line, telling local checks from AI ones.
18
+ * Given the rules that ran, it can also render one fixing hint per rule
19
+ * that reported a violation, in first-appearance order.
17
20
  */
18
21
  class Report {
19
- constructor(tool, violations, millis = null) {
22
+ constructor(tool, violations, millis = null, label = '') {
20
23
  this.tool = tool;
21
24
  this.bag = violations;
22
25
  this.millis = millis;
26
+ this.label = label;
23
27
  }
24
28
  count() {
25
29
  return this.bag.length;
26
30
  }
27
31
  text() {
28
32
  const suffix = this.millis === null ? '' : ` in ${prettyMs(this.millis)}`;
29
- return this.bag
33
+ const prefix = this.label === '' ? '' : `${this.label}: `;
34
+ const lines = this.bag
30
35
  .map((violation) => violation.text())
31
- .concat(`${this.bag.length} problems found${suffix}`)
36
+ .concat(`${prefix}${this.bag.length} problems found${suffix}`);
37
+ if (this.bag.length > 0) {
38
+ lines.push(
39
+ 'Spotted a false positive? dogent is in beta, please report it at ' +
40
+ 'https://github.com/yegor256/dogent/issues'
41
+ );
42
+ }
43
+ return lines.join('\n');
44
+ }
45
+ hints(rules) {
46
+ const byId = new Map(rules.map((rule) => [rule.id, rule]));
47
+ const seen = [];
48
+ this.bag.forEach((violation) => {
49
+ if (!seen.includes(violation.rule) && byId.has(violation.rule)) {
50
+ seen.push(violation.rule);
51
+ }
52
+ });
53
+ return seen
54
+ .map((id) => `[${id}]: ${byId.get(id).hint()}`)
32
55
  .join('\n');
33
56
  }
34
57
  sarif() {
@@ -24,6 +24,9 @@ class AmbiguousOr {
24
24
  constructor() {
25
25
  this.id = 'ambiguous-or';
26
26
  }
27
+ hint() {
28
+ return 'Replace every either-or choice such as and/or or a slashed alternative with one explicit branch, naming exactly which option the agent must take so no guessing remains.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag either-or ambiguity beyond "and/or" and slashed alternatives, and state exactly which option applies`;
29
32
  }
@@ -24,6 +24,9 @@ class Atomic {
24
24
  constructor() {
25
25
  this.id = 'atomic';
26
26
  }
27
+ hint() {
28
+ return 'Split a line that bundles several instructions into one line per instruction, since the agent reads each line as a single command and welded clauses get half-followed.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag any line that carries more than one instruction, counting distinct clauses whether or not a semicolon, "and", or "then" welds them, yet never count a coordinated object or noun phrase trailing "and" or "then" as a second instruction`;
29
32
  }
@@ -24,6 +24,9 @@ class Budget {
24
24
  this.id = 'budget';
25
25
  this.cap = cap;
26
26
  }
27
+ hint() {
28
+ return 'Trim or split the manifesto so it holds fewer instructions than the budget, moving secondary guidance into separate referenced files to keep the core small.';
29
+ }
27
30
  prompt() {
28
31
  return '';
29
32
  }
@@ -20,6 +20,9 @@ class Command {
20
20
  constructor() {
21
21
  this.id = 'command';
22
22
  }
23
+ hint() {
24
+ return 'Rewrite the line as a direct imperative that opens with a base-form verb such as Write, Strip, or Keep, dropping any pronoun, question, or plain statement.';
25
+ }
23
26
  prompt() {
24
27
  return `${this.id}: flag any line that reads as a description, a question, or a plain statement rather than a direct order; a line opening with a base-form imperative verb, such as "Write", "Strip", "Drop", or "Keep", is itself a direct order and must never be flagged`;
25
28
  }
@@ -25,6 +25,9 @@ class Concise {
25
25
  this.id = 'concise';
26
26
  this.max = max;
27
27
  }
28
+ hint() {
29
+ return 'Shorten the file or split its detail into referenced files so its middle instructions survive, since models attend to the start and end and skim the middle.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: flag a manifesto so long its middle instructions risk being lost, and recommend splitting detail into referenced files`;
30
33
  }
@@ -23,6 +23,9 @@ class Conditional {
23
23
  constructor() {
24
24
  this.id = 'conditional';
25
25
  }
26
+ hint() {
27
+ return 'Break a line that packs several conditions into one case per line, so the agent never has to untangle a whole decision tree welded onto a single line.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: flag implicit branching that carries no keyword, and split each case into its own command`;
28
31
  }
@@ -19,6 +19,9 @@ class Consistent {
19
19
  constructor() {
20
20
  this.id = 'consistent';
21
21
  }
22
+ hint() {
23
+ return 'Delete the duplicate instruction or reconcile the contradictory pair, so each instruction is stated once and never both ordered and forbidden across two lines.';
24
+ }
22
25
  prompt() {
23
26
  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
27
  }
@@ -25,6 +25,9 @@ class CounterExample {
25
25
  constructor() {
26
26
  this.id = 'counter-example';
27
27
  }
28
+ hint() {
29
+ return 'Remove the demonstration of the wrong form and show only the correct form, since displaying a mistake can teach the agent to repeat it.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: judge whether each example shows the correct behavior, and flag any example that demonstrates the incorrect form`;
30
33
  }
@@ -25,6 +25,9 @@ class Crowded {
25
25
  this.id = 'crowded';
26
26
  this.limit = limit;
27
27
  }
28
+ hint() {
29
+ return 'Split an overcrowded section into smaller sections, each holding only a handful of related instructions under its own short heading.';
30
+ }
28
31
  prompt() {
29
32
  return '';
30
33
  }
@@ -57,8 +57,11 @@ class DeadImport {
57
57
  this.id = 'dead-import';
58
58
  this.depth = 5;
59
59
  }
60
+ hint() {
61
+ return 'Fix or remove the @path import so it points to a real file, and break any circular or overly deep import chain the host tool cannot resolve.';
62
+ }
60
63
  prompt() {
61
- return `${this.id}: flag any @path/to/file import that points to no file on disk`;
64
+ return `${this.id}: flag any @path/to/file import that points to no file on disk; only an @-prefixed token counts as an import, so never treat a bare path in prose as one`;
62
65
  }
63
66
  violations(document) {
64
67
  const uri = document.uri();
@@ -23,6 +23,9 @@ class Default {
23
23
  constructor() {
24
24
  this.id = 'default';
25
25
  }
26
+ hint() {
27
+ return 'State the default outcome whenever you mark behavior optional, telling the agent exactly what to do when it declines the option.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: flag optionality that names no default even without a listed marker, and state the default`;
28
31
  }
@@ -25,6 +25,9 @@ class DescriptionLength {
25
25
  this.id = 'description-length';
26
26
  this.ceiling = 1024;
27
27
  }
28
+ hint() {
29
+ return 'Write a SKILL.md description that is neither empty nor bloated, stating the capability concisely so it fits the loader budget.';
30
+ }
28
31
  prompt() {
29
32
  return '';
30
33
  }
@@ -22,6 +22,9 @@ class DescriptionTriggers {
22
22
  this.id = 'description-triggers';
23
23
  this.minimum = 20;
24
24
  }
25
+ hint() {
26
+ return 'Name the concrete situations and user phrases that should activate the skill in its description, so the loader knows exactly when to invoke it.';
27
+ }
25
28
  prompt() {
26
29
  return `${this.id}: in a SKILL.md, flag a description that is too short or fails to name the concrete situations and user phrases that should activate the skill, even when it contains the word "when"`;
27
30
  }
@@ -27,6 +27,9 @@ class DescriptionVoice {
27
27
  this.id = 'description-voice';
28
28
  this.pronoun = /\b(?:I|we|you|your|my|our)\b/giu;
29
29
  }
30
+ hint() {
31
+ return 'Write the SKILL.md description as a third-person capability statement such as Extracts tables, never in first or second person.';
32
+ }
30
33
  prompt() {
31
34
  return `${this.id}: in a SKILL.md, flag a description written in first or second person and demand a third-person capability statement`;
32
35
  }
package/src/rules/done.js CHANGED
@@ -21,6 +21,9 @@ class Done {
21
21
  constructor() {
22
22
  this.id = 'done';
23
23
  }
24
+ hint() {
25
+ return 'Add a verifiable, pass-or-fail completion check to the SKILL.md so the agent knows exactly how to confirm the work is finished.';
26
+ }
24
27
  prompt() {
25
28
  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
29
  }
@@ -28,6 +28,9 @@ class DuplicateSection {
28
28
  constructor() {
29
29
  this.id = 'duplicate-section';
30
30
  }
31
+ hint() {
32
+ return 'Give every section a distinct heading, merging or renaming any two sections that share the same name.';
33
+ }
31
34
  prompt() {
32
35
  return '';
33
36
  }
@@ -27,6 +27,9 @@ class Emoji {
27
27
  this.id = 'emoji';
28
28
  this.glyph = /[\p{Extended_Pictographic}\u{2190}-\u{21FF}\u{2300}-\u{27BF}\u{2B00}-\u{2BFF}]/gu;
29
29
  }
30
+ hint() {
31
+ return 'Delete decorative emoji and pictographic symbols, since they add token noise without carrying any instruction, and keep the text plain.';
32
+ }
30
33
  prompt() {
31
34
  return '';
32
35
  }
@@ -25,6 +25,9 @@ class Emphasis {
25
25
  this.id = 'emphasis';
26
26
  this.shout = new Set(['IMPORTANT', 'ALWAYS', 'NEVER', 'MUST', 'CRITICAL', 'REQUIRED']);
27
27
  }
28
+ hint() {
29
+ return 'Drop shouting such as all-caps words or repeated exclamation marks and state the instruction plainly, since volume adds no meaning for the model.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: flag emphatic shouting the patterns miss, including borderline all-caps and reward framing, since emphasis adds no instruction`;
30
33
  }
@@ -23,6 +23,9 @@ class Empty {
23
23
  constructor() {
24
24
  this.id = 'empty';
25
25
  }
26
+ hint() {
27
+ return 'Fill the hollow section with at least one instruction, or delete the heading, so no section declares itself without a body.';
28
+ }
26
29
  prompt() {
27
30
  return '';
28
31
  }
@@ -21,6 +21,9 @@ class ExampleFormat {
21
21
  constructor() {
22
22
  this.id = 'example-format';
23
23
  }
24
+ hint() {
25
+ return 'Make the example in the SKILL.md conform exactly to the declared output format, since a mismatched example teaches the agent the wrong shape.';
26
+ }
24
27
  prompt() {
25
28
  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
29
  }
@@ -24,6 +24,9 @@ class Example {
24
24
  constructor() {
25
25
  this.id = 'example';
26
26
  }
27
+ hint() {
28
+ return 'Add at least one concrete worked input and output example to the SKILL.md, since a single demonstration guides the agent far better than prose alone.';
29
+ }
27
30
  prompt() {
28
31
  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
32
  }
@@ -23,6 +23,9 @@ class ExternalLink {
23
23
  constructor() {
24
24
  this.id = 'external-link';
25
25
  }
26
+ hint() {
27
+ return 'Inline the durable guidance instead of linking to an external URL, since the page may rot or smuggle hidden instructions at run time.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: judge whether an external link is load-bearing, and flag durable guidance that should be inlined instead`;
28
31
  }
@@ -24,6 +24,9 @@ class FenceLanguage {
24
24
  this.id = 'fence-language';
25
25
  this.fence = /^\s*(?:```|~~~)\s*(?<lang>\S*)/u;
26
26
  }
27
+ hint() {
28
+ return 'Declare a language right after the opening fence of every code block so readers and tooling know the snippet syntax.';
29
+ }
27
30
  prompt() {
28
31
  return '';
29
32
  }
@@ -26,6 +26,9 @@ class Format {
26
26
  constructor() {
27
27
  this.id = 'format';
28
28
  }
29
+ hint() {
30
+ return 'Declare and show the exact output format whenever the skill generates output, since a pinned-down contract makes structured output far more reliable.';
31
+ }
29
32
  prompt() {
30
33
  return `${this.id}: in a SKILL.md, judge whether the declared output format is concrete and machine-checkable, and flag a generating skill that pins down no format`;
31
34
  }
@@ -22,6 +22,9 @@ class Frontmatter {
22
22
  this.required = required;
23
23
  this.allowed = allowed;
24
24
  }
25
+ hint() {
26
+ return 'Open the file with a YAML frontmatter block that declares every required key with a real value and carries no key outside the allowed set.';
27
+ }
25
28
  prompt() {
26
29
  return `${this.id}: in a ${this.name} file, flag any required key whose value is empty, vague, or a leftover placeholder`;
27
30
  }
@@ -21,6 +21,9 @@ class Grouped {
21
21
  constructor() {
22
22
  this.id = 'grouped';
23
23
  }
24
+ hint() {
25
+ return 'Move every loose instruction under a section heading, since prose before the first heading belongs to no section.';
26
+ }
24
27
  prompt() {
25
28
  return '';
26
29
  }
@@ -22,6 +22,9 @@ class Hedging {
22
22
  constructor() {
23
23
  this.id = 'hedging';
24
24
  }
25
+ hint() {
26
+ return 'Remove hedging words such as should, just, or usually and state the order firmly, since timid wording weakens the command.';
27
+ }
25
28
  prompt() {
26
29
  return `${this.id}: flag soft, non-committal, or hedging wording, including conditional escape hatches and vague scope that carry no fixed hedge word`;
27
30
  }
@@ -26,6 +26,9 @@ class HiddenChar {
26
26
  this.id = 'hidden-char';
27
27
  this.hidden = /[\u200B-\u200D\uFEFF\u202A-\u202E\u2066-\u2069\uFE00-\uFE0F\u{E0100}-\u{E01EF}]/gu;
28
28
  }
29
+ hint() {
30
+ return 'Delete the invisible or control character named by its codepoint, since hidden characters can corrupt parsing or smuggle instructions.';
31
+ }
29
32
  prompt() {
30
33
  return '';
31
34
  }
@@ -28,6 +28,9 @@ class Homoglyph {
28
28
  this.latin = /[A-Za-z]/u;
29
29
  this.confusable = /[Ѐ-ӿͰ-Ͽ＀-￯]/u;
30
30
  }
31
+ hint() {
32
+ return 'Replace the mixed-script look-alike character with its plain ASCII equivalent, since a foreign codepoint hidden inside a word breaks tooling.';
33
+ }
31
34
  prompt() {
32
35
  return '';
33
36
  }
@@ -34,6 +34,9 @@ class InlineCode {
34
34
  constructor() {
35
35
  this.id = 'inline-code';
36
36
  }
37
+ hint() {
38
+ return 'Wrap a bare literal token, such as a command, path, filename, or flag, in backticks so the model treats it as a literal and never rewords it.';
39
+ }
37
40
  prompt() {
38
41
  return `${this.id}: flag a bare literal token (command, path, filename, or flag) that should be wrapped in backticks, judging borderline cases`;
39
42
  }
@@ -48,6 +48,8 @@ const defined = (masked) => {
48
48
  while (hit !== null) {
49
49
  if (hit.groups.acronym) {
50
50
  found.add(hit.groups.acronym);
51
+ } else if (/^[A-Z]{2,}$/u.test(hit.groups.gloss)) {
52
+ found.add(hit.groups.gloss);
51
53
  } else {
52
54
  found.add(initials(hit.groups.gloss));
53
55
  }
@@ -64,10 +66,11 @@ const undefining = (acronym, scope) => !scope.known.has(acronym) &&
64
66
  *
65
67
  * Flags an acronym that lands in prose without ever being expanded. An
66
68
  * acronym counts as defined when the document, anywhere, follows it with
67
- * a parenthetical gloss, as in "RBAC (role-based access control)", or when
69
+ * a parenthetical gloss, as in "RBAC (role-based access control)", when
68
70
  * a parenthetical's word initials spell it, as in "AAA pattern
69
- * (Arrange-Act-Assert)", so a single expansion licenses every later
70
- * mention. Well-known acronyms sit
71
+ * (Arrange-Act-Assert)", or when the expansion precedes a parenthetical
72
+ * acronym, as in "Virtual Private Network (VPN)", so a single expansion
73
+ * licenses every later mention. Well-known acronyms sit
71
74
  * in a built-in allowlist and pass untouched. Only the first unexpanded
72
75
  * occurrence of each acronym is reported. Its prompt hands non-acronym
73
76
  * domain jargon, the rare nouns a reader cannot parse, to the AI oracle.
@@ -76,6 +79,9 @@ class Jargon {
76
79
  constructor() {
77
80
  this.id = 'jargon';
78
81
  }
82
+ hint() {
83
+ return 'Expand each acronym on first use with a parenthetical gloss, and replace rare domain jargon with plain words a fresh reader can parse.';
84
+ }
79
85
  prompt() {
80
86
  return `${this.id}: flag non-acronym domain jargon, rare nouns a fresh reader cannot parse, and ask for a plain-word definition on first use`;
81
87
  }
@@ -22,6 +22,9 @@ class LineLength {
22
22
  this.id = 'line-length';
23
23
  this.max = max;
24
24
  }
25
+ hint() {
26
+ return 'Shorten the line below the width cap, splitting it into separate instructions if needed so each stays easy to read.';
27
+ }
25
28
  prompt() {
26
29
  return '';
27
30
  }
@@ -24,6 +24,9 @@ class MetaReference {
24
24
  this.id = 'meta-reference';
25
25
  this.phrase = /\b(?:as an ai|as a language model|you are an ai|you are a model|this prompt|these instructions|this manifesto|the system prompt)\b/giu;
26
26
  }
27
+ hint() {
28
+ return 'Delete self-referential framing such as as an AI or this prompt, since it narrates the setup instead of issuing a command.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag self-referential framing of the model or document beyond the fixed list, and delete it`;
29
32
  }
@@ -23,6 +23,9 @@ class NameFormat {
23
23
  constructor() {
24
24
  this.id = 'name-format';
25
25
  }
26
+ hint() {
27
+ return 'Write the SKILL.md frontmatter name in kebab-case, using only lowercase letters and digits joined by single hyphens.';
28
+ }
26
29
  prompt() {
27
30
  return '';
28
31
  }
@@ -24,6 +24,9 @@ class NameMatchesDir {
24
24
  constructor() {
25
25
  this.id = 'name-matches-dir';
26
26
  }
27
+ hint() {
28
+ return 'Rename the SKILL.md frontmatter name so it matches the name of the directory that holds the file.';
29
+ }
27
30
  prompt() {
28
31
  return '';
29
32
  }
@@ -19,6 +19,9 @@ class NoArticles {
19
19
  constructor() {
20
20
  this.id = 'no-articles';
21
21
  }
22
+ hint() {
23
+ return 'Remove filler articles such as a, an, and the, since they add noise without changing the instruction.';
24
+ }
22
25
  prompt() {
23
26
  return `${this.id}: flag filler or noise words that add nothing to an instruction`;
24
27
  }
@@ -24,6 +24,9 @@ class Ordered {
24
24
  constructor() {
25
25
  this.id = 'ordered';
26
26
  }
27
+ hint() {
28
+ return 'Convert a sequence of steps into a numbered list, since models follow numbered ordered steps far more reliably than unordered bullets.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag an implied sequence that no marker word signals, demanding a numbered list whenever the order of steps matters`;
29
32
  }
@@ -21,6 +21,9 @@ class Passive {
21
21
  constructor() {
22
22
  this.id = 'passive';
23
23
  }
24
+ hint() {
25
+ return 'Rewrite the line in active imperative voice, naming the action to take instead of describing what gets done.';
26
+ }
24
27
  prompt() {
25
28
  return `${this.id}: flag any instruction written in passive voice, judging true grammatical voice including irregular past participles a fixed pattern misses`;
26
29
  }
@@ -24,6 +24,9 @@ class Persona {
24
24
  constructor() {
25
25
  this.id = 'persona';
26
26
  }
27
+ hint() {
28
+ return 'Delete the role-play persona such as You are a senior engineer, since assigning a role adds no instruction and can hurt performance.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag indirect persona or role-play framing that assigns the agent a role with no fixed keyword, since a persona adds no instruction`;
29
32
  }
@@ -27,6 +27,9 @@ class Placement {
27
27
  this.id = 'placement';
28
28
  this.keyword = /^#{1,6}\s+.*\b(?:safety|security|mission|critical|constraints?)\b/iu;
29
29
  }
30
+ hint() {
31
+ return 'Move the critical section to the top or bottom of the file, since models attend least to the buried middle of a long context.';
32
+ }
30
33
  prompt() {
31
34
  return `${this.id}: identify the single most important instruction and judge whether it sits near the top or bottom of the file rather than buried in the middle`;
32
35
  }
@@ -24,6 +24,9 @@ class Polite {
24
24
  constructor() {
25
25
  this.id = 'polite';
26
26
  }
27
+ hint() {
28
+ return 'Remove courtesy and scaffolding phrases such as please or make sure to, since they waste tokens and weaken the command.';
29
+ }
27
30
  prompt() {
28
31
  return '';
29
32
  }
@@ -19,14 +19,18 @@ const mask = require('../mask');
19
19
  * "Only use real data" beats "Don't use mock data". Its prompt hands
20
20
  * subtler bans, those carrying no head keyword, to the AI
21
21
  * oracle, which rewrites a prohibition with no keyword as a positive
22
- * command.
22
+ * command. The prompt demands an actual negation before flagging, so
23
+ * an affirmative imperative that already states what to do stays clean.
23
24
  */
24
25
  class Positive {
25
26
  constructor() {
26
27
  this.id = 'positive';
27
28
  }
29
+ hint() {
30
+ return 'Rewrite a prohibition as a positive imperative stating what to do, since a ban forces the model to process the forbidden idea first.';
31
+ }
28
32
  prompt() {
29
- return `${this.id}: flag any instruction phrased as a prohibition, including bans carrying no fixed keyword, and rewrite each as a positive imperative`;
33
+ return `${this.id}: flag an instruction only when it forbids or negates an action, including a ban that carries no fixed keyword, and rewrite each as a positive imperative; leave an affirmative imperative that already states what to do untouched, returning nothing for it`;
30
34
  }
31
35
  violations(document) {
32
36
  const uri = document.uri();
@@ -25,6 +25,9 @@ class PseudoHeading {
25
25
  constructor() {
26
26
  this.id = 'pseudo-heading';
27
27
  }
28
+ hint() {
29
+ return 'Replace a bold line posing as a heading with a real level-2 heading marked by two hashes.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: flag any bold line posing as a section heading, deferring borderline label-versus-instruction calls to the oracle, and demand a real level-2 "##" heading`;
30
33
  }
@@ -19,6 +19,9 @@ class Punctuation {
19
19
  constructor() {
20
20
  this.id = 'punctuation';
21
21
  }
22
+ hint() {
23
+ return 'Write each instruction as one complete sentence that opens with a capital letter and closes with a period.';
24
+ }
22
25
  prompt() {
23
26
  return `${this.id}: flag any instruction that is not one complete, grammatical sentence`;
24
27
  }
@@ -25,6 +25,9 @@ class Quantifier {
25
25
  constructor() {
26
26
  this.id = 'quantifier';
27
27
  }
28
+ hint() {
29
+ return 'Replace a vague quantity word such as some or several with an exact number or threshold the agent can act on.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: flag a vague amount that names no exact count even without a listed word, and propose a concrete number or threshold to replace it`;
30
33
  }
@@ -23,6 +23,9 @@ class Rationale {
23
23
  constructor() {
24
24
  this.id = 'rationale';
25
25
  }
26
+ hint() {
27
+ return 'Delete the explanation or convert it into a direct order, since a manifesto carries commands, not justifications.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: flag any line that explains a reason, motivation, or benefit instead of issuing a direct order, even when it carries no fixed marker, and convert each into a command or delete it`;
28
31
  }
@@ -54,6 +54,9 @@ class Redundant {
54
54
  this.id = 'redundant';
55
55
  this.phrases = phrases;
56
56
  }
57
+ hint() {
58
+ return 'Delete the line that restates default model behavior, since generic advice the model already knows wastes the context budget.';
59
+ }
57
60
  prompt() {
58
61
  return `${this.id}: flag any line that restates default agent behavior already known to the model, not a project-specific instruction, including reworded paraphrases that match no fixed phrase list`;
59
62
  }
@@ -31,6 +31,9 @@ class Referential {
31
31
  'iu'
32
32
  );
33
33
  }
34
+ hint() {
35
+ return 'Open the line by naming its own subject instead of a bare pronoun, since a dangling pronoun points at another line and breaks one line, one instruction.';
36
+ }
34
37
  prompt() {
35
38
  return `${this.id}: flag any line whose subject is a pronoun with no antecedent on the same line, including mid-line dangling references a head pattern misses`;
36
39
  }
@@ -20,6 +20,9 @@ class Scope {
20
20
  constructor() {
21
21
  this.id = 'scope';
22
22
  }
23
+ hint() {
24
+ return 'Split a SKILL.md that mixes unrelated responsibilities into separate single-purpose skills, since reliability scales with a narrow scope.';
25
+ }
23
26
  prompt() {
24
27
  return `${this.id}: in a SKILL.md, judge whether the sections describe a single coherent responsibility or several unrelated ones, and recommend a split when they diverge`;
25
28
  }
@@ -23,6 +23,9 @@ class SectionLevel {
23
23
  constructor() {
24
24
  this.id = 'section-level';
25
25
  }
26
+ hint() {
27
+ return 'Make every section a level-2 heading marked by two hashes, allowing only one optional top-level title to open the file.';
28
+ }
26
29
  prompt() {
27
30
  return '';
28
31
  }
@@ -32,6 +32,9 @@ class SelfContained {
32
32
  'iu'
33
33
  );
34
34
  }
35
+ hint() {
36
+ return 'Replace a relative cross-reference such as see above with a concrete named target, so the line survives reordering and chunking.';
37
+ }
35
38
  prompt() {
36
39
  return `${this.id}: flag any line leaning on a relative cross-reference such as "see above" that breaks when the file is reordered or chunked, deferring subtler dangling references to the oracle`;
37
40
  }
@@ -21,6 +21,9 @@ class ShortSections {
21
21
  constructor() {
22
22
  this.id = 'short-sections';
23
23
  }
24
+ hint() {
25
+ return 'Trim every section heading to a label of one to three words so the manifesto reads as a map, not as prose.';
26
+ }
24
27
  prompt() {
25
28
  return '';
26
29
  }
@@ -39,6 +39,9 @@ class Simple {
39
39
  constructor() {
40
40
  this.id = 'simple';
41
41
  }
42
+ hint() {
43
+ return 'Split a tangled multi-clause sentence into several short, simple lines so the grammar leaves no room for ambiguity.';
44
+ }
42
45
  prompt() {
43
46
  return `${this.id}: flag any grammatically tangled, multi-clause instruction, judging true clause depth even when the line carries few commas or conjunctions`;
44
47
  }
@@ -24,6 +24,9 @@ class Stale {
24
24
  constructor() {
25
25
  this.id = 'stale';
26
26
  }
27
+ hint() {
28
+ return 'Replace a volatile time or version reference such as currently or a pinned version number with a durable rule that never rots.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag any implicit time-bound or version-bound claim that carries no keyword, and propose a durable rule that never rots`;
29
32
  }
@@ -29,6 +29,9 @@ class Terms {
29
29
  ['function', 'method', 'routine']
30
30
  ];
31
31
  }
32
+ hint() {
33
+ return 'Pick one canonical term for each concept and use it everywhere, since naming the same idea two ways makes the agent guess they differ.';
34
+ }
32
35
  prompt() {
33
36
  return `${this.id}: flag any pair of words used interchangeably for one concept, and demand a single canonical term across the whole file`;
34
37
  }
@@ -21,6 +21,9 @@ class TokenCount {
21
21
  this.id = 'token-count';
22
22
  this.cap = cap;
23
23
  }
24
+ hint() {
25
+ return 'Cut bloated wording across the file so its total token count fits the cap, keeping every instruction terse.';
26
+ }
24
27
  prompt() {
25
28
  return `${this.id}: flag bloated wording that wastes the context budget`;
26
29
  }
@@ -23,6 +23,9 @@ class ToolClarity {
23
23
  constructor() {
24
24
  this.id = 'tool-clarity';
25
25
  }
26
+ hint() {
27
+ return 'Name the exact tool, path, or invocation instead of a generic noun like the script, so the agent never guesses which one to run.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: flag any vague reference to a tool or command beyond the fixed list, and demand the exact name, path, or invocation instead`;
28
31
  }
@@ -23,6 +23,9 @@ class Transition {
23
23
  constructor() {
24
24
  this.id = 'transition';
25
25
  }
26
+ hint() {
27
+ return 'Delete a discourse connector such as furthermore or however that opens a line, since it chains prose without adding a command.';
28
+ }
26
29
  prompt() {
27
30
  return `${this.id}: flag connective filler beyond the fixed list, and delete it`;
28
31
  }
@@ -25,6 +25,9 @@ class Unfinished {
25
25
  constructor() {
26
26
  this.id = 'unfinished';
27
27
  }
28
+ hint() {
29
+ return 'Resolve every leftover marker such as TODO, a placeholder, or a trailing ellipsis, since they signal half-finished work.';
30
+ }
28
31
  prompt() {
29
32
  return '';
30
33
  }
@@ -31,6 +31,9 @@ class Unique {
31
31
  constructor() {
32
32
  this.id = 'unique';
33
33
  }
34
+ hint() {
35
+ return 'Remove the repeated instruction and state each rule once, since duplicated guidance wastes context and can drift out of sync.';
36
+ }
34
37
  prompt() {
35
38
  return `${this.id}: flag any instruction that repeats another instruction in the file, including two lines that carry the same meaning in different words, not only lines matching after normalized case, punctuation, and word order`;
36
39
  }
@@ -33,6 +33,9 @@ class Units {
33
33
  'u'
34
34
  );
35
35
  }
36
+ hint() {
37
+ return 'State the unit beside every magnitude, such as 80 symbols, so the reader knows what the number measures.';
38
+ }
36
39
  prompt() {
37
40
  return `${this.id}: flag a magnitude whose unit is implicit even in context, and state what it measures`;
38
41
  }
@@ -26,6 +26,9 @@ class Untrusted {
26
26
  constructor() {
27
27
  this.id = 'untrusted';
28
28
  }
29
+ hint() {
30
+ return 'Add a data-only guard when an instruction consumes external content, telling the agent to treat it as untrusted and never follow embedded instructions.';
31
+ }
29
32
  prompt() {
30
33
  return `${this.id}: judge whether a consumed source is genuinely untrusted external input and whether its data-only guard is sufficient against prompt injection`;
31
34
  }
@@ -24,6 +24,9 @@ class Vague {
24
24
  constructor() {
25
25
  this.id = 'vague';
26
26
  }
27
+ hint() {
28
+ return 'Replace a subjective qualifier such as properly or clean with a concrete, checkable threshold the agent can measure.';
29
+ }
27
30
  prompt() {
28
31
  return `${this.id}: flag any subjective or unmeasurable qualifier beyond the fixed list, and propose a concrete, checkable threshold to replace it`;
29
32
  }
@@ -25,6 +25,9 @@ class WeakVerb {
25
25
  constructor() {
26
26
  this.id = 'weak-verb';
27
27
  }
28
+ hint() {
29
+ return 'Replace a vague leading verb such as handle or manage with a precise action verb that names exactly what to do.';
30
+ }
28
31
  prompt() {
29
32
  return `${this.id}: flag a leading imperative verb that names no concrete action beyond the fixed list, and propose a precise action verb`;
30
33
  }
package/src/sources.js CHANGED
@@ -26,6 +26,9 @@ class Sources {
26
26
  files() {
27
27
  const found = [];
28
28
  this.paths.forEach((entry) => {
29
+ if (!fs.existsSync(entry)) {
30
+ throw new Error(`No such file or directory: ${entry}`);
31
+ }
29
32
  if (fs.statSync(entry).isDirectory()) {
30
33
  this.scan(entry, found);
31
34
  } else {
package/src/version.js CHANGED
@@ -9,8 +9,8 @@
9
9
  * Version.
10
10
  *
11
11
  * The current release of dogent, replaced on every release by rultor.
12
- * The default `0.11.0` marks an unreleased build straight from source.
12
+ * The default `0.12.1` marks an unreleased build straight from source.
13
13
  */
14
- const version = '0.11.0';
14
+ const version = '0.12.1';
15
15
 
16
16
  module.exports = version;