@yegor256/dogent 0.9.0 → 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.
- package/README.md +17 -0
- package/package.json +3 -2
- package/src/args.js +21 -5
- package/src/dogent.js +23 -13
- package/src/report.js +8 -2
- package/src/rules/budget.js +50 -0
- package/src/rules/concise.js +48 -0
- package/src/rules/counter-example.js +60 -0
- package/src/rules/done.js +53 -0
- package/src/rules/emphasis.js +81 -0
- package/src/rules/example.js +60 -0
- package/src/rules/format.js +68 -0
- package/src/rules/index.js +40 -0
- package/src/rules/jargon.js +105 -0
- package/src/rules/name-matches-dir.js +1 -1
- package/src/rules/ordered.js +57 -0
- package/src/rules/persona.js +55 -0
- package/src/rules/positive.js +57 -0
- package/src/rules/pseudo-heading.js +55 -0
- package/src/rules/rationale.js +54 -0
- package/src/rules/referential.js +67 -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/untrusted.js +59 -0
- package/src/rules/vague.js +63 -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
|
+
* 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;
|
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.10.0` marks an unreleased build straight from source.
|
|
13
13
|
*/
|
|
14
|
-
const version = '0.
|
|
14
|
+
const version = '0.10.0';
|
|
15
15
|
|
|
16
16
|
module.exports = version;
|