@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.
- package/README.md +104 -12
- package/package.json +3 -2
- package/src/args.js +35 -4
- package/src/defaults.js +47 -0
- package/src/dogent.js +42 -16
- package/src/openai.js +8 -5
- package/src/prompt.js +0 -4
- package/src/report.js +8 -2
- package/src/rules/ambiguous-or.js +58 -0
- package/src/rules/budget.js +50 -0
- package/src/rules/concise.js +48 -0
- package/src/rules/conditional.js +55 -0
- package/src/rules/consistent.js +1 -1
- package/src/rules/counter-example.js +60 -0
- package/src/rules/default.js +60 -0
- package/src/rules/description-length.js +64 -0
- package/src/rules/description-voice.js +67 -0
- package/src/rules/done.js +53 -0
- package/src/rules/duplicate-section.js +65 -0
- package/src/rules/emoji.js +60 -0
- package/src/rules/emphasis.js +81 -0
- package/src/rules/example-format.js +32 -0
- package/src/rules/example.js +60 -0
- package/src/rules/external-link.js +57 -0
- package/src/rules/fence-language.js +55 -0
- package/src/rules/format.js +68 -0
- package/src/rules/hidden-char.js +61 -0
- package/src/rules/homoglyph.js +82 -0
- package/src/rules/index.js +80 -0
- package/src/rules/inline-code.js +79 -0
- package/src/rules/jargon.js +115 -0
- package/src/rules/meta-reference.js +57 -0
- package/src/rules/ordered.js +57 -0
- package/src/rules/persona.js +55 -0
- package/src/rules/placement.js +62 -0
- package/src/rules/positive.js +57 -0
- package/src/rules/pseudo-heading.js +55 -0
- package/src/rules/quantifier.js +63 -0
- package/src/rules/rationale.js +54 -0
- package/src/rules/referential.js +67 -0
- package/src/rules/scope.js +31 -0
- package/src/rules/self-contained.js +66 -0
- package/src/rules/stale.js +62 -0
- package/src/rules/terms.js +77 -0
- package/src/rules/tool-clarity.js +61 -0
- package/src/rules/transition.js +59 -0
- package/src/rules/units.js +81 -0
- package/src/rules/untrusted.js +59 -0
- package/src/rules/vague.js +63 -0
- package/src/rules/weak-verb.js +62 -0
- package/src/version.js +2 -2
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
* Terms.
|
|
14
|
+
*
|
|
15
|
+
* Flags synonym drift: one concept named several ways across a file.
|
|
16
|
+
* Where consistent hunts word-for-word duplicates and contradictions,
|
|
17
|
+
* this rule catches the same idea wearing different labels, which makes
|
|
18
|
+
* the agent guess whether two names mean two things. A small built-in
|
|
19
|
+
* map of synonym groups seeds the deterministic check; the broader,
|
|
20
|
+
* fuzzier judgement is handed to the AI oracle through prompt().
|
|
21
|
+
*/
|
|
22
|
+
class Terms {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.id = 'terms';
|
|
25
|
+
this.groups = [
|
|
26
|
+
['agent', 'assistant', 'bot'],
|
|
27
|
+
['directory', 'folder'],
|
|
28
|
+
['parameter', 'argument', 'param', 'arg'],
|
|
29
|
+
['function', 'method', 'routine']
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
prompt() {
|
|
33
|
+
return `${this.id}: flag any pair of words used interchangeably for one concept, and demand a single canonical term across the whole file`;
|
|
34
|
+
}
|
|
35
|
+
violations(document) {
|
|
36
|
+
const uri = document.uri();
|
|
37
|
+
const seen = this.groups.map(() => new Map());
|
|
38
|
+
document.walk({
|
|
39
|
+
header: () => [],
|
|
40
|
+
prose: (text, line) => this.collect(text, line, seen),
|
|
41
|
+
snippet: () => [],
|
|
42
|
+
bullets: () => [],
|
|
43
|
+
frontmatter: () => []
|
|
44
|
+
});
|
|
45
|
+
return this.report(seen, uri);
|
|
46
|
+
}
|
|
47
|
+
collect(text, line, seen) {
|
|
48
|
+
const masked = mask(text);
|
|
49
|
+
this.groups.forEach((group, index) => {
|
|
50
|
+
group.forEach((word) => {
|
|
51
|
+
const regex = new RegExp(`\\b${word}\\b`, 'iu');
|
|
52
|
+
if (regex.test(masked) && !seen[index].has(word)) {
|
|
53
|
+
seen[index].set(word, line);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
report(seen, uri) {
|
|
60
|
+
const found = [];
|
|
61
|
+
seen.forEach((members) => {
|
|
62
|
+
if (members.size >= 2) {
|
|
63
|
+
const names = Array.from(members.keys());
|
|
64
|
+
const lines = Array.from(members.values());
|
|
65
|
+
found.push(new Violation(
|
|
66
|
+
this.id,
|
|
67
|
+
'warning',
|
|
68
|
+
`concept named two ways ("${names[0]}"/"${names[1]}"), pick one term`,
|
|
69
|
+
new Region(uri, lines[1], 1)
|
|
70
|
+
));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = Terms;
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
* ToolClarity.
|
|
14
|
+
*
|
|
15
|
+
* Demands the exact name of a tool or command, never a bare generic
|
|
16
|
+
* noun. An action verb such as "run", "use", or "invoke" pointed at
|
|
17
|
+
* "the script" or "a command" leaves the agent guessing which one. The
|
|
18
|
+
* mask blanks backticked spans first, so "run `npm test`" passes while
|
|
19
|
+
* "run the script" gets flagged. Its prompt defers subtler vague
|
|
20
|
+
* references to the AI oracle.
|
|
21
|
+
*/
|
|
22
|
+
class ToolClarity {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.id = 'tool-clarity';
|
|
25
|
+
}
|
|
26
|
+
prompt() {
|
|
27
|
+
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
|
+
}
|
|
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 found = [];
|
|
41
|
+
const regex = new RegExp(
|
|
42
|
+
'\\b(?:run|use|call|invoke|execute|open)\\s+(?:the|a|an)\\s+' +
|
|
43
|
+
'(?:script|tool|command|api|file|function)\\b',
|
|
44
|
+
'giu'
|
|
45
|
+
);
|
|
46
|
+
const masked = mask(text);
|
|
47
|
+
let hit = regex.exec(masked);
|
|
48
|
+
while (hit !== null) {
|
|
49
|
+
found.push(new Violation(
|
|
50
|
+
this.id,
|
|
51
|
+
'warning',
|
|
52
|
+
`name the exact tool or command, not "${hit[0]}"`,
|
|
53
|
+
new Region(uri, line, hit.index + 1)
|
|
54
|
+
));
|
|
55
|
+
hit = regex.exec(masked);
|
|
56
|
+
}
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = ToolClarity;
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* Transition.
|
|
14
|
+
*
|
|
15
|
+
* Flags a discourse connector that opens an instruction, like
|
|
16
|
+
* "furthermore", "in summary", or "however". A leading connector
|
|
17
|
+
* chains prose without adding a command, so it earns one violation.
|
|
18
|
+
* The check stays disjoint from the polite rule: it lists no courtesy
|
|
19
|
+
* word such as "please" or "make sure to". Its prompt hands looser
|
|
20
|
+
* connective filler to the AI oracle for deletion.
|
|
21
|
+
*/
|
|
22
|
+
class Transition {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.id = 'transition';
|
|
25
|
+
}
|
|
26
|
+
prompt() {
|
|
27
|
+
return `${this.id}: flag connective filler beyond the fixed list, and 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 masked = mask(text);
|
|
41
|
+
const [lead] = masked.match(/^\s*(?:[-*+]|\d+\.)?\s*/u);
|
|
42
|
+
const rest = masked.slice(lead.length);
|
|
43
|
+
const regex = /^(?:furthermore|moreover|additionally|in summary|in conclusion|in other words|as you can see|of course|basically|essentially|that said|however)\b/iu;
|
|
44
|
+
const hit = regex.exec(rest);
|
|
45
|
+
if (hit === null) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
new Violation(
|
|
50
|
+
this.id,
|
|
51
|
+
'warning',
|
|
52
|
+
`discourse transition "${hit[0]}" adds no instruction, delete it`,
|
|
53
|
+
new Region(uri, line, lead.length + 1)
|
|
54
|
+
)
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = Transition;
|
|
@@ -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
|
+
* Units.
|
|
14
|
+
*
|
|
15
|
+
* Demands that every magnitude carry a unit so the reader knows what
|
|
16
|
+
* the number measures. A bare cardinal like "under 80" leaves the
|
|
17
|
+
* scale implicit; "under 80 symbols" states it. The checker masks
|
|
18
|
+
* inline code first, then scans each run of digits and flags one that
|
|
19
|
+
* names no unit, skipping percentages, units already present, decimals
|
|
20
|
+
* and versions, and leading list ordinals. Distinct from quantifier,
|
|
21
|
+
* which targets vague amount words like "several"; here the number is
|
|
22
|
+
* exact but its unit is missing.
|
|
23
|
+
*/
|
|
24
|
+
class Units {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.id = 'units';
|
|
27
|
+
this.unit = new RegExp(
|
|
28
|
+
'^ ?(?:ms|s|sec|secs|second|seconds|min|mins|minute|minutes|' +
|
|
29
|
+
'h|hr|hrs|hour|hours|d|day|days|week|weeks|month|months|' +
|
|
30
|
+
'year|years|b|kb|mb|gb|tb|byte|bytes|bit|bits|char|chars|' +
|
|
31
|
+
'character|characters|symbol|symbols|line|lines|word|words|' +
|
|
32
|
+
'token|tokens|px|em|rem|pt|time|times|x)\\b',
|
|
33
|
+
'u'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
prompt() {
|
|
37
|
+
return `${this.id}: flag a magnitude whose unit is implicit even in context, and state what it measures`;
|
|
38
|
+
}
|
|
39
|
+
violations(document) {
|
|
40
|
+
const uri = document.uri();
|
|
41
|
+
return document.walk({
|
|
42
|
+
header: () => [],
|
|
43
|
+
prose: (text, line) => this.scan(text, line, uri),
|
|
44
|
+
snippet: () => [],
|
|
45
|
+
bullets: () => [],
|
|
46
|
+
frontmatter: () => []
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
scan(text, line, uri) {
|
|
50
|
+
const clean = mask(text);
|
|
51
|
+
const found = [];
|
|
52
|
+
const digits = /\d+/gu;
|
|
53
|
+
let hit = digits.exec(clean);
|
|
54
|
+
while (hit !== null) {
|
|
55
|
+
if (!this.skip(clean, hit)) {
|
|
56
|
+
found.push(new Violation(
|
|
57
|
+
this.id,
|
|
58
|
+
'warning',
|
|
59
|
+
`number "${hit[0]}" has no unit, state what it measures`,
|
|
60
|
+
new Region(uri, line, hit.index + 1)
|
|
61
|
+
));
|
|
62
|
+
}
|
|
63
|
+
hit = digits.exec(clean);
|
|
64
|
+
}
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
skip(clean, hit) {
|
|
68
|
+
const after = clean.slice(hit.index + hit[0].length);
|
|
69
|
+
if (after.startsWith('%') || this.unit.test(after)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const before = clean.charAt(hit.index - 1);
|
|
73
|
+
if (/[.v@\d]/u.test(before) || /^\.\d/u.test(after)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const lead = /^\s*(?<num>\d+)\.\s/u.exec(clean);
|
|
77
|
+
return lead !== null && lead.index + lead[0].indexOf(lead.groups.num) === hit.index;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = Units;
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* Untrusted.
|
|
14
|
+
*
|
|
15
|
+
* Guards against indirect prompt injection. When a line tells the agent
|
|
16
|
+
* to act on external content — a verb like "read", "fetch", "open",
|
|
17
|
+
* "follow", or "execute" applied to a "page", "url", "link", "email",
|
|
18
|
+
* "file", "issue", "output", or "comment" — that content can carry
|
|
19
|
+
* hidden instructions the agent then obeys. A standalone checker flags
|
|
20
|
+
* such a line when it lacks a data-only guard ("as data", "do not
|
|
21
|
+
* follow", "treat as untrusted", "inside delimiters"). Its prompt hands
|
|
22
|
+
* the deeper judgement of source trust and guard sufficiency to the AI
|
|
23
|
+
* oracle.
|
|
24
|
+
*/
|
|
25
|
+
class Untrusted {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.id = 'untrusted';
|
|
28
|
+
}
|
|
29
|
+
prompt() {
|
|
30
|
+
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
|
+
}
|
|
32
|
+
violations(document) {
|
|
33
|
+
const uri = document.uri();
|
|
34
|
+
return document.walk({
|
|
35
|
+
header: () => [],
|
|
36
|
+
prose: (text, line) => this.scan(text, line, uri),
|
|
37
|
+
snippet: () => [],
|
|
38
|
+
bullets: () => [],
|
|
39
|
+
frontmatter: () => []
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
scan(text, line, uri) {
|
|
43
|
+
const masked = mask(text);
|
|
44
|
+
const verb = /\b(?:read|fetch|open|follow|execute)\b/iu;
|
|
45
|
+
const source = /\b(?:page|url|link|email|file|issue|output|comment)\b/iu;
|
|
46
|
+
const guard = /\b(?:as data|do not follow|treat as untrusted|inside delimiters|untrusted)\b/iu;
|
|
47
|
+
if (!verb.test(masked) || !source.test(masked) || guard.test(masked)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return [new Violation(
|
|
51
|
+
this.id,
|
|
52
|
+
'warning',
|
|
53
|
+
'untrusted input consumed without a data-only guard',
|
|
54
|
+
new Region(uri, line, 1)
|
|
55
|
+
)];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = Untrusted;
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
* Vague.
|
|
14
|
+
*
|
|
15
|
+
* Flags subjective, unmeasurable qualifiers that pretend to be precise:
|
|
16
|
+
* "properly", "good", "clean", "fast", "robust", and the like. Each
|
|
17
|
+
* leaves the agent to guess a criterion that varies run to run, so a
|
|
18
|
+
* vague qualifier is a non-instruction in disguise. The list is kept
|
|
19
|
+
* apart from the hedging words so the two rules never double-report. Its
|
|
20
|
+
* prompt hands subjective adjectives outside the fixed list to the AI
|
|
21
|
+
* oracle, asking it to suggest a concrete, checkable threshold.
|
|
22
|
+
*/
|
|
23
|
+
class Vague {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.id = 'vague';
|
|
26
|
+
}
|
|
27
|
+
prompt() {
|
|
28
|
+
return `${this.id}: flag any subjective or unmeasurable qualifier beyond the fixed list, and propose a concrete, checkable threshold to replace it`;
|
|
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(?:properly|correctly|appropriately|good|clean|fast|slow|' +
|
|
44
|
+
'large|small|robust|reasonable|efficient|as much as possible|' +
|
|
45
|
+
'if needed)\\b',
|
|
46
|
+
'giu'
|
|
47
|
+
);
|
|
48
|
+
const masked = mask(text);
|
|
49
|
+
let hit = regex.exec(masked);
|
|
50
|
+
while (hit !== null) {
|
|
51
|
+
found.push(new Violation(
|
|
52
|
+
this.id,
|
|
53
|
+
'warning',
|
|
54
|
+
`vague qualifier "${hit[0]}" carries no measurable criterion`,
|
|
55
|
+
new Region(uri, line, hit.index + 1)
|
|
56
|
+
));
|
|
57
|
+
hit = regex.exec(masked);
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = Vague;
|
|
@@ -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
|
+
* Weak verb.
|
|
14
|
+
*
|
|
15
|
+
* Flags a leading imperative that names no concrete action: "handle",
|
|
16
|
+
* "manage", "process", "support", "ensure", "maintain", "deal with",
|
|
17
|
+
* "take care of", "work on". Each tells the agent to do something
|
|
18
|
+
* unspecified, so the line carries no real instruction. The check fires
|
|
19
|
+
* only on the first word of the line, leaving the same verb mid-sentence
|
|
20
|
+
* alone, and stays apart from "vague" (adjectives) and "command"
|
|
21
|
+
* (imperative form). Its prompt hands subtler catch-all verbs outside
|
|
22
|
+
* the fixed list to the AI oracle.
|
|
23
|
+
*/
|
|
24
|
+
class WeakVerb {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.id = 'weak-verb';
|
|
27
|
+
}
|
|
28
|
+
prompt() {
|
|
29
|
+
return `${this.id}: flag a leading imperative verb that names no concrete action beyond the fixed list, and propose a precise action verb`;
|
|
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
|
+
const regex = new RegExp(
|
|
44
|
+
'^(?<lead>\\s*(?:[-*+]|\\d+\\.)?\\s*)' +
|
|
45
|
+
'(?<verb>handle|manage|process|support|ensure|maintain|' +
|
|
46
|
+
'deal with|take care of|work on)\\b',
|
|
47
|
+
'iu'
|
|
48
|
+
);
|
|
49
|
+
const hit = regex.exec(masked);
|
|
50
|
+
if (hit === null) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return [new Violation(
|
|
54
|
+
this.id,
|
|
55
|
+
'warning',
|
|
56
|
+
`weak verb "${hit.groups.verb}" names no concrete action, use a precise verb`,
|
|
57
|
+
new Region(uri, line, hit.groups.lead.length + 1)
|
|
58
|
+
)];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = WeakVerb;
|
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.
|
|
12
|
+
* The default `0.11.0` marks an unreleased build straight from source.
|
|
13
13
|
*/
|
|
14
|
-
const version = '0.
|
|
14
|
+
const version = '0.11.0';
|
|
15
15
|
|
|
16
16
|
module.exports = version;
|