@yegor256/dogent 0.9.1 → 0.10.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.
@@ -21,6 +21,7 @@ const NameMatchesDir = require('./name-matches-dir');
21
21
  const Polite = require('./polite');
22
22
  const Unfinished = require('./unfinished');
23
23
  const Crowded = require('./crowded');
24
+ const Budget = require('./budget');
24
25
  const DescriptionTriggers = require('./description-triggers');
25
26
  const Atomic = require('./atomic');
26
27
  const Hedging = require('./hedging');
@@ -29,6 +30,25 @@ const Unique = require('./unique');
29
30
  const Consistent = require('./consistent');
30
31
  const Simple = require('./simple');
31
32
  const SectionLevel = require('./section-level');
33
+ const Format = require('./format');
34
+ const Untrusted = require('./untrusted');
35
+ const Ordered = require('./ordered');
36
+ const Emphasis = require('./emphasis');
37
+ const Persona = require('./persona');
38
+ const Concise = require('./concise');
39
+ const Example = require('./example');
40
+ const Referential = require('./referential');
41
+ const Vague = require('./vague');
42
+ const Positive = require('./positive');
43
+ const Done = require('./done');
44
+ const Terms = require('./terms');
45
+ const Jargon = require('./jargon');
46
+ const PseudoHeading = require('./pseudo-heading');
47
+ const Stale = require('./stale');
48
+ const ToolClarity = require('./tool-clarity');
49
+ const CounterExample = require('./counter-example');
50
+ const Rationale = require('./rationale');
51
+ const SelfContained = require('./self-contained');
32
52
 
33
53
  module.exports = () => [
34
54
  new Grouped(),
@@ -37,6 +57,7 @@ module.exports = () => [
37
57
  new SectionLevel(),
38
58
  new LineLength(80),
39
59
  new TokenCount(4000),
60
+ new Concise(200),
40
61
  new NoArticles(),
41
62
  new Command(),
42
63
  new Punctuation(),
@@ -44,14 +65,33 @@ module.exports = () => [
44
65
  new Redundant(),
45
66
  new Consistent(),
46
67
  new Simple(),
68
+ new Referential(),
47
69
  new NameMatchesDir(),
48
70
  new Polite(),
49
71
  new Unfinished(),
50
72
  new Crowded(10),
73
+ new Budget(60),
51
74
  new DescriptionTriggers(),
75
+ new Example(),
76
+ new Format(),
52
77
  new Atomic(),
78
+ new Ordered(),
53
79
  new Hedging(),
80
+ new Vague(),
81
+ new ToolClarity(),
54
82
  new Passive(),
83
+ new Untrusted(),
84
+ new Emphasis(),
85
+ new Persona(),
86
+ new Positive(),
87
+ new Done(),
88
+ new Terms(),
89
+ new Jargon(),
90
+ new PseudoHeading(),
91
+ new Stale(),
92
+ new CounterExample(),
93
+ new Rationale(),
94
+ new SelfContained(),
55
95
  new Unique(),
56
96
  new Frontmatter(
57
97
  'SKILL.md',
@@ -0,0 +1,105 @@
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
+ const ALLOWLIST = new Set([
13
+ 'AI',
14
+ 'CI',
15
+ 'CD',
16
+ 'CLI',
17
+ 'API',
18
+ 'URL',
19
+ 'URI',
20
+ 'HTTP',
21
+ 'HTTPS',
22
+ 'JSON',
23
+ 'YAML',
24
+ 'XML',
25
+ 'HTML',
26
+ 'CSS',
27
+ 'SQL',
28
+ 'ID',
29
+ 'OK',
30
+ 'OS',
31
+ 'IO',
32
+ 'NPM',
33
+ 'PR',
34
+ 'MIT',
35
+ 'SARIF',
36
+ 'SKILL',
37
+ 'CLAUDE'
38
+ ]);
39
+
40
+ const defined = (masked) => {
41
+ const found = new Set();
42
+ const regex = /\b(?<acronym>[A-Z]{2,})\s*\(/gu;
43
+ let hit = regex.exec(masked);
44
+ while (hit !== null) {
45
+ found.add(hit.groups.acronym);
46
+ hit = regex.exec(masked);
47
+ }
48
+ return found;
49
+ };
50
+
51
+ const undefining = (acronym, scope) => !scope.known.has(acronym) &&
52
+ !ALLOWLIST.has(acronym);
53
+
54
+ /**
55
+ * Jargon.
56
+ *
57
+ * Flags an acronym that lands in prose without ever being expanded. An
58
+ * acronym counts as defined when the document, anywhere, follows it with
59
+ * a parenthetical gloss, as in "RBAC (role-based access control)", so a
60
+ * single expansion licenses every later mention. Well-known acronyms sit
61
+ * in a built-in allowlist and pass untouched. Only the first unexpanded
62
+ * occurrence of each acronym is reported. Its prompt hands non-acronym
63
+ * domain jargon, the rare nouns a reader cannot parse, to the AI oracle.
64
+ */
65
+ class Jargon {
66
+ constructor() {
67
+ this.id = 'jargon';
68
+ }
69
+ prompt() {
70
+ 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`;
71
+ }
72
+ violations(document) {
73
+ const uri = document.uri();
74
+ const known = defined(mask(document.text()));
75
+ const seen = new Set();
76
+ return document.walk({
77
+ header: () => [],
78
+ prose: (text, line) => this.scan(text, line, {uri, known, seen}),
79
+ snippet: () => [],
80
+ bullets: () => [],
81
+ frontmatter: () => []
82
+ });
83
+ }
84
+ scan(text, line, scope) {
85
+ const hits = [...mask(text).matchAll(/\b[A-Z]{2,}\b/gu)];
86
+ return hits.reduce((found, hit) => {
87
+ const [acronym] = hit;
88
+ const novel = !scope.seen.has(acronym) && undefining(acronym, scope);
89
+ scope.seen.add(acronym);
90
+ return novel
91
+ ? found.concat(this.flag(acronym, new Region(scope.uri, line, hit.index + 1)))
92
+ : found;
93
+ }, []);
94
+ }
95
+ flag(acronym, region) {
96
+ return new Violation(
97
+ this.id,
98
+ 'warning',
99
+ `acronym "${acronym}" never expanded, define it on first use`,
100
+ region
101
+ );
102
+ }
103
+ }
104
+
105
+ module.exports = Jargon;
@@ -0,0 +1,57 @@
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
+ * Ordered.
14
+ *
15
+ * Demands a numbered list when order matters. Models follow numbered,
16
+ * sequentially ordered steps far more reliably than unordered bullets,
17
+ * and shuffling steps drops accuracy sharply. A standalone checker flags
18
+ * an unordered bullet item that carries a sequence marker like "first",
19
+ * "then", "next", "after that", "finally", or "step 2", since the order
20
+ * is real but the structure hides it. Its prompt hands implicit ordering
21
+ * with no marker word to the AI oracle.
22
+ */
23
+ class Ordered {
24
+ constructor() {
25
+ this.id = 'ordered';
26
+ }
27
+ prompt() {
28
+ return `${this.id}: flag an implied sequence that no marker word signals, demanding a numbered list whenever the order of steps matters`;
29
+ }
30
+ violations(document) {
31
+ const uri = document.uri();
32
+ return document.walk({
33
+ header: () => [],
34
+ prose: (text, line) => this.scan(text, line, uri),
35
+ snippet: () => [],
36
+ bullets: () => [],
37
+ frontmatter: () => []
38
+ });
39
+ }
40
+ scan(text, line, uri) {
41
+ if (!/^\s*[-*+]\s+/u.test(text)) {
42
+ return [];
43
+ }
44
+ const markers = /\b(?:first|second|third|then|next|after that|afterwards|finally|lastly|step\s+\d+)\b/iu;
45
+ if (!markers.test(mask(text))) {
46
+ return [];
47
+ }
48
+ return [new Violation(
49
+ this.id,
50
+ 'warning',
51
+ 'sequence detected, use a numbered list to fix the order',
52
+ new Region(uri, line, 1)
53
+ )];
54
+ }
55
+ }
56
+
57
+ module.exports = Ordered;
@@ -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
+ * Persona.
14
+ *
15
+ * Flags gratuitous role-play that opens a manifesto with a persona:
16
+ * "You are a senior engineer", "Act as an expert reviewer", and the
17
+ * like. The largest controlled study finds personas do not improve task
18
+ * performance and can hurt, so a role-play line is pure context bloat
19
+ * that adds no instruction. A standalone checker flags the line whose
20
+ * head assigns the agent a role; its prompt hands the indirect persona
21
+ * framing the regex misses to the AI oracle.
22
+ */
23
+ class Persona {
24
+ constructor() {
25
+ this.id = 'persona';
26
+ }
27
+ prompt() {
28
+ 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
+ }
30
+ violations(document) {
31
+ const uri = document.uri();
32
+ return document.walk({
33
+ header: () => [],
34
+ prose: (text, line) => this.scan(text, line, uri),
35
+ snippet: () => [],
36
+ bullets: () => [],
37
+ frontmatter: () => []
38
+ });
39
+ }
40
+ scan(text, line, uri) {
41
+ const regex = /^(?<marker>\s*(?:[-*+]|\d+\.)\s+)?(?:you are an? |act as |imagine you are |pretend to be |as an? \w+,)/iu;
42
+ const hit = regex.exec(mask(text));
43
+ if (hit === null) {
44
+ return [];
45
+ }
46
+ return [new Violation(
47
+ this.id,
48
+ 'warning',
49
+ 'persona assignment adds no instruction, delete it',
50
+ new Region(uri, line, (hit.groups.marker || '').length + 1)
51
+ )];
52
+ }
53
+ }
54
+
55
+ module.exports = Persona;
@@ -0,0 +1,57 @@
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
+ * Positive.
14
+ *
15
+ * Demands positive, goal-oriented imperatives over bans. A standalone
16
+ * checker flags a line whose head is an obvious prohibition: "do not",
17
+ * "don't", "never", "avoid", "refrain from", "must not", or "no longer".
18
+ * A ban forces the model to process the forbidden concept first, so
19
+ * "Only use real data" beats "Don't use mock data". Its prompt hands
20
+ * subtler bans, those carrying no head keyword, to the AI
21
+ * oracle, which rewrites a prohibition with no keyword as a positive
22
+ * command.
23
+ */
24
+ class Positive {
25
+ constructor() {
26
+ this.id = 'positive';
27
+ }
28
+ 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`;
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 = /^(?<marker>\s*(?:[-*+]|\d+\.)\s+)?(?:do not|don't|never|avoid|refrain from|must not|no longer)\b/iu;
43
+ const hit = regex.exec(mask(text));
44
+ if (hit === null) {
45
+ return [];
46
+ }
47
+ const marker = hit.groups.marker || '';
48
+ return [new Violation(
49
+ this.id,
50
+ 'warning',
51
+ 'negative phrasing detected, state the positive command instead',
52
+ new Region(uri, line, marker.length + 1)
53
+ )];
54
+ }
55
+ }
56
+
57
+ module.exports = Positive;
@@ -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
+
11
+ /**
12
+ * PseudoHeading.
13
+ *
14
+ * Rejects a bold line posing as a section heading, such as
15
+ * "**Setup:**" standing alone. The whole line, once an optional list
16
+ * marker drops, must sit inside one emphasis run, wrapped by "**" or
17
+ * "__" and ending in an optional colon. A line carrying only inline
18
+ * bold inside other words, like "Use **bold** sparingly.", stays free,
19
+ * since the emphasis wraps a fragment, not the whole label.
20
+ *
21
+ * Its prompt hands borderline label-versus-instruction calls to the AI
22
+ * oracle.
23
+ */
24
+ class PseudoHeading {
25
+ constructor() {
26
+ this.id = 'pseudo-heading';
27
+ }
28
+ prompt() {
29
+ 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
+ }
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 body = text.trim().replace(/^(?:[-*+]|\d+\.)\s+/u, '');
43
+ if (!/^(?<fence>\*\*|__)(?!\s)(?:.+?)(?<!\s)\k<fence>:?$/u.test(body)) {
44
+ return [];
45
+ }
46
+ return [new Violation(
47
+ this.id,
48
+ 'warning',
49
+ 'bold pseudo-heading found, use a level-2 "##" heading',
50
+ new Region(uri, line, 1)
51
+ )];
52
+ }
53
+ }
54
+
55
+ module.exports = PseudoHeading;
@@ -0,0 +1,54 @@
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
+ * Rationale.
14
+ *
15
+ * Demands orders, not explanations. A standalone checker flags a line
16
+ * that opens with a justification marker such as "because", "the
17
+ * reason", "this keeps", "this ensures", "this helps", "so that", or
18
+ * "in order to", since such a line argues a point instead of issuing a
19
+ * command. Justification belongs in commit messages and design docs, so
20
+ * its prompt hands subtler explanation-only lines to the AI oracle.
21
+ */
22
+ class Rationale {
23
+ constructor() {
24
+ this.id = 'rationale';
25
+ }
26
+ prompt() {
27
+ 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
+ }
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 clean = mask(text).replace(/^\s*(?:[-*+]|\d+\.)\s+/u, '');
41
+ const regex = /^(?:because|the reason|this keeps|this ensures|this helps|so that|in order to)\b/iu;
42
+ if (!regex.test(clean)) {
43
+ return [];
44
+ }
45
+ return [new Violation(
46
+ this.id,
47
+ 'warning',
48
+ 'rationale carries no command, delete or convert to an order',
49
+ new Region(uri, line, 1)
50
+ )];
51
+ }
52
+ }
53
+
54
+ module.exports = Rationale;
@@ -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
+ const mask = require('../mask');
11
+
12
+ /**
13
+ * Referential.
14
+ *
15
+ * Demands that every line name its own subject. A standalone checker
16
+ * flags a line that opens with a bare pronoun acting as the subject:
17
+ * "it", "they", and "them" always, and "this", "that", "these", or
18
+ * "those" only when a verb follows rather than a noun, so a determiner
19
+ * like "These rules stay final" stays clean. Such a pronoun points at a
20
+ * previous line, breaking the "one line, one instruction" contract. Its
21
+ * prompt hands the subtler mid-line dangling references to the AI oracle.
22
+ */
23
+ class Referential {
24
+ constructor() {
25
+ this.id = 'referential';
26
+ this.verbs = new RegExp(
27
+ '^(?:is|are|was|were|be|been|being|will|would|can|could|shall|' +
28
+ 'should|must|may|might|has|have|had|do|does|did|only|then|runs|' +
29
+ 'run|applies|apply|happens|happen|means|requires|needs|comes|' +
30
+ 'goes|makes|breaks|points|refers)$',
31
+ 'iu'
32
+ );
33
+ }
34
+ prompt() {
35
+ 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
+ }
37
+ violations(document) {
38
+ const uri = document.uri();
39
+ return document.walk({
40
+ header: () => [],
41
+ prose: (text, line) => this.scan(text, line, uri),
42
+ snippet: () => [],
43
+ bullets: () => [],
44
+ frontmatter: () => []
45
+ });
46
+ }
47
+ scan(text, line, uri) {
48
+ const regex = /^(?<marker>\s*(?:[-*+]|\d+\.)\s+)?(?<pro>it|this|that|they|them|these|those)\b\s+(?<next>[\w']+)/iu;
49
+ const hit = regex.exec(mask(text));
50
+ if (hit === null) {
51
+ return [];
52
+ }
53
+ const pronoun = hit.groups.pro.toLowerCase();
54
+ const ambiguous = /^(?:this|that|these|those)$/u.test(pronoun);
55
+ if (ambiguous && !this.verbs.test(hit.groups.next)) {
56
+ return [];
57
+ }
58
+ return [new Violation(
59
+ this.id,
60
+ 'warning',
61
+ `pronoun "${hit.groups.pro}" has no antecedent on this line, name the subject`,
62
+ new Region(uri, line, (hit.groups.marker || '').length + 1)
63
+ )];
64
+ }
65
+ }
66
+
67
+ module.exports = Referential;
@@ -0,0 +1,66 @@
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
+ * SelfContained.
14
+ *
15
+ * Demands that every line stand on its own without leaning on its
16
+ * neighbours. A standalone checker flags a relative cross-reference
17
+ * phrase like "see above", "as mentioned below", or "the previous
18
+ * step" that breaks the moment the file is reordered or chunked. A
19
+ * line pointing somewhere concrete through a markdown link stays
20
+ * clean. Distinct from referential, which targets bare pronouns; this
21
+ * one targets positional cross-references. Its prompt hands subtler
22
+ * dangling references to the AI oracle.
23
+ */
24
+ class SelfContained {
25
+ constructor() {
26
+ this.id = 'self-contained';
27
+ this.phrase = new RegExp(
28
+ 'mentioned above|mentioned below|see above|see below|' +
29
+ 'as discussed|the section above|the section below|' +
30
+ 'the previous step|as stated earlier|mentioned earlier|' +
31
+ 'refer to the guide',
32
+ 'iu'
33
+ );
34
+ }
35
+ prompt() {
36
+ 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
+ }
38
+ violations(document) {
39
+ const uri = document.uri();
40
+ return document.walk({
41
+ header: () => [],
42
+ prose: (text, line) => this.scan(text, line, uri),
43
+ snippet: () => [],
44
+ bullets: () => [],
45
+ frontmatter: () => []
46
+ });
47
+ }
48
+ scan(text, line, uri) {
49
+ const clean = mask(text);
50
+ if (clean.includes('](')) {
51
+ return [];
52
+ }
53
+ const hit = this.phrase.exec(clean);
54
+ if (hit === null) {
55
+ return [];
56
+ }
57
+ return [new Violation(
58
+ this.id,
59
+ 'warning',
60
+ `relative reference "${hit[0]}" breaks when reordered, name the target`,
61
+ new Region(uri, line, hit.index + 1)
62
+ )];
63
+ }
64
+ }
65
+
66
+ module.exports = SelfContained;
@@ -0,0 +1,62 @@
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
+ * Stale.
14
+ *
15
+ * Flags volatile time and version references that rot over time:
16
+ * words like "currently", "now", "today", "recently", and hardcoded
17
+ * version literals such as "18.17.0". Each pins an instruction to a
18
+ * moment or release that drifts, so the manifesto silently ages. The
19
+ * rule scans only prose, never fenced snippets, so version pins inside
20
+ * code blocks survive untouched. Its prompt hands implicit time-bound
21
+ * claims with no keyword to the AI oracle.
22
+ */
23
+ class Stale {
24
+ constructor() {
25
+ this.id = 'stale';
26
+ }
27
+ prompt() {
28
+ 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
+ }
30
+ violations(document) {
31
+ const uri = document.uri();
32
+ return document.walk({
33
+ header: () => [],
34
+ prose: (text, line) => this.scan(text, line, uri),
35
+ snippet: () => [],
36
+ bullets: () => [],
37
+ frontmatter: () => []
38
+ });
39
+ }
40
+ scan(text, line, uri) {
41
+ const found = [];
42
+ const regex = new RegExp(
43
+ '\\b(?:currently|now|today|recently|lately|at present|as of|' +
44
+ 'the latest)\\b|\\bv?\\d+\\.\\d+(?:\\.\\d+)?\\b',
45
+ 'giu'
46
+ );
47
+ const masked = mask(text);
48
+ let hit = regex.exec(masked);
49
+ while (hit !== null) {
50
+ found.push(new Violation(
51
+ this.id,
52
+ 'warning',
53
+ `volatile reference "${hit[0]}" will rot, state a durable rule`,
54
+ new Region(uri, line, hit.index + 1)
55
+ ));
56
+ hit = regex.exec(masked);
57
+ }
58
+ return found;
59
+ }
60
+ }
61
+
62
+ module.exports = Stale;