@yegor256/dogent 0.10.0 → 0.12.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 +135 -15
- package/package.json +1 -1
- package/src/args.js +24 -4
- package/src/defaults.js +47 -0
- package/src/dogent.js +65 -18
- package/src/openai.js +8 -5
- package/src/prompt.js +0 -4
- package/src/report.js +26 -3
- package/src/rules/ambiguous-or.js +61 -0
- package/src/rules/atomic.js +3 -0
- package/src/rules/budget.js +3 -0
- package/src/rules/command.js +3 -0
- package/src/rules/concise.js +3 -0
- package/src/rules/conditional.js +58 -0
- package/src/rules/consistent.js +4 -1
- package/src/rules/counter-example.js +3 -0
- package/src/rules/crowded.js +3 -0
- package/src/rules/dead-import.js +3 -0
- package/src/rules/default.js +63 -0
- package/src/rules/description-length.js +67 -0
- package/src/rules/description-triggers.js +3 -0
- package/src/rules/description-voice.js +70 -0
- package/src/rules/done.js +3 -0
- package/src/rules/duplicate-section.js +68 -0
- package/src/rules/emoji.js +63 -0
- package/src/rules/emphasis.js +3 -0
- package/src/rules/empty.js +3 -0
- package/src/rules/example-format.js +35 -0
- package/src/rules/example.js +3 -0
- package/src/rules/external-link.js +60 -0
- package/src/rules/fence-language.js +58 -0
- package/src/rules/format.js +3 -0
- package/src/rules/frontmatter.js +3 -0
- package/src/rules/grouped.js +3 -0
- package/src/rules/hedging.js +3 -0
- package/src/rules/hidden-char.js +64 -0
- package/src/rules/homoglyph.js +85 -0
- package/src/rules/index.js +40 -0
- package/src/rules/inline-code.js +82 -0
- package/src/rules/jargon.js +17 -4
- package/src/rules/line-length.js +3 -0
- package/src/rules/meta-reference.js +60 -0
- package/src/rules/name-format.js +3 -0
- package/src/rules/name-matches-dir.js +3 -0
- package/src/rules/no-articles.js +3 -0
- package/src/rules/ordered.js +3 -0
- package/src/rules/passive.js +3 -0
- package/src/rules/persona.js +3 -0
- package/src/rules/placement.js +65 -0
- package/src/rules/polite.js +3 -0
- package/src/rules/positive.js +3 -0
- package/src/rules/pseudo-heading.js +3 -0
- package/src/rules/punctuation.js +3 -0
- package/src/rules/quantifier.js +66 -0
- package/src/rules/rationale.js +3 -0
- package/src/rules/redundant.js +3 -0
- package/src/rules/referential.js +3 -0
- package/src/rules/scope.js +34 -0
- package/src/rules/section-level.js +3 -0
- package/src/rules/self-contained.js +3 -0
- package/src/rules/short-sections.js +3 -0
- package/src/rules/simple.js +3 -0
- package/src/rules/stale.js +3 -0
- package/src/rules/terms.js +3 -0
- package/src/rules/token-count.js +3 -0
- package/src/rules/tool-clarity.js +3 -0
- package/src/rules/transition.js +62 -0
- package/src/rules/unfinished.js +3 -0
- package/src/rules/unique.js +3 -0
- package/src/rules/units.js +84 -0
- package/src/rules/untrusted.js +3 -0
- package/src/rules/vague.js +3 -0
- package/src/rules/weak-verb.js +65 -0
- package/src/sources.js +3 -0
- package/src/version.js +2 -2
package/src/rules/referential.js
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Scope.
|
|
10
|
+
*
|
|
11
|
+
* Demands that one SKILL.md stay bound to a single coherent
|
|
12
|
+
* responsibility. Agent reliability scales with specialisation: a
|
|
13
|
+
* well-scoped single-stage skill beats a monolith that conflates
|
|
14
|
+
* unrelated subtasks. Whether the sections cohere or diverge is not
|
|
15
|
+
* visible line by line, so this check is pure judgement: prompt()
|
|
16
|
+
* defers the verdict to the AI oracle and violations() finds nothing
|
|
17
|
+
* on its own.
|
|
18
|
+
*/
|
|
19
|
+
class Scope {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.id = 'scope';
|
|
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
|
+
}
|
|
26
|
+
prompt() {
|
|
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`;
|
|
28
|
+
}
|
|
29
|
+
violations() {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = Scope;
|
|
@@ -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
|
}
|
package/src/rules/simple.js
CHANGED
|
@@ -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
|
}
|
package/src/rules/stale.js
CHANGED
|
@@ -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
|
}
|
package/src/rules/terms.js
CHANGED
|
@@ -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
|
}
|
package/src/rules/token-count.js
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
+
* 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
|
+
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
|
+
}
|
|
29
|
+
prompt() {
|
|
30
|
+
return `${this.id}: flag connective filler beyond the fixed list, and delete it`;
|
|
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 [lead] = masked.match(/^\s*(?:[-*+]|\d+\.)?\s*/u);
|
|
45
|
+
const rest = masked.slice(lead.length);
|
|
46
|
+
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;
|
|
47
|
+
const hit = regex.exec(rest);
|
|
48
|
+
if (hit === null) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
new Violation(
|
|
53
|
+
this.id,
|
|
54
|
+
'warning',
|
|
55
|
+
`discourse transition "${hit[0]}" adds no instruction, delete it`,
|
|
56
|
+
new Region(uri, line, lead.length + 1)
|
|
57
|
+
)
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = Transition;
|
package/src/rules/unfinished.js
CHANGED
package/src/rules/unique.js
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
hint() {
|
|
37
|
+
return 'State the unit beside every magnitude, such as 80 symbols, so the reader knows what the number measures.';
|
|
38
|
+
}
|
|
39
|
+
prompt() {
|
|
40
|
+
return `${this.id}: flag a magnitude whose unit is implicit even in context, and state what it measures`;
|
|
41
|
+
}
|
|
42
|
+
violations(document) {
|
|
43
|
+
const uri = document.uri();
|
|
44
|
+
return document.walk({
|
|
45
|
+
header: () => [],
|
|
46
|
+
prose: (text, line) => this.scan(text, line, uri),
|
|
47
|
+
snippet: () => [],
|
|
48
|
+
bullets: () => [],
|
|
49
|
+
frontmatter: () => []
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
scan(text, line, uri) {
|
|
53
|
+
const clean = mask(text);
|
|
54
|
+
const found = [];
|
|
55
|
+
const digits = /\d+/gu;
|
|
56
|
+
let hit = digits.exec(clean);
|
|
57
|
+
while (hit !== null) {
|
|
58
|
+
if (!this.skip(clean, hit)) {
|
|
59
|
+
found.push(new Violation(
|
|
60
|
+
this.id,
|
|
61
|
+
'warning',
|
|
62
|
+
`number "${hit[0]}" has no unit, state what it measures`,
|
|
63
|
+
new Region(uri, line, hit.index + 1)
|
|
64
|
+
));
|
|
65
|
+
}
|
|
66
|
+
hit = digits.exec(clean);
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
skip(clean, hit) {
|
|
71
|
+
const after = clean.slice(hit.index + hit[0].length);
|
|
72
|
+
if (after.startsWith('%') || this.unit.test(after)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const before = clean.charAt(hit.index - 1);
|
|
76
|
+
if (/[.v@\d]/u.test(before) || /^\.\d/u.test(after)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const lead = /^\s*(?<num>\d+)\.\s/u.exec(clean);
|
|
80
|
+
return lead !== null && lead.index + lead[0].indexOf(lead.groups.num) === hit.index;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = Units;
|
package/src/rules/untrusted.js
CHANGED
|
@@ -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
|
}
|
package/src/rules/vague.js
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const Violation = require('../violation');
|
|
9
|
+
const Region = require('../region');
|
|
10
|
+
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
|
+
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
|
+
}
|
|
31
|
+
prompt() {
|
|
32
|
+
return `${this.id}: flag a leading imperative verb that names no concrete action beyond the fixed list, and propose a precise action verb`;
|
|
33
|
+
}
|
|
34
|
+
violations(document) {
|
|
35
|
+
const uri = document.uri();
|
|
36
|
+
return document.walk({
|
|
37
|
+
header: () => [],
|
|
38
|
+
prose: (text, line) => this.scan(text, line, uri),
|
|
39
|
+
snippet: () => [],
|
|
40
|
+
bullets: () => [],
|
|
41
|
+
frontmatter: () => []
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
scan(text, line, uri) {
|
|
45
|
+
const masked = mask(text);
|
|
46
|
+
const regex = new RegExp(
|
|
47
|
+
'^(?<lead>\\s*(?:[-*+]|\\d+\\.)?\\s*)' +
|
|
48
|
+
'(?<verb>handle|manage|process|support|ensure|maintain|' +
|
|
49
|
+
'deal with|take care of|work on)\\b',
|
|
50
|
+
'iu'
|
|
51
|
+
);
|
|
52
|
+
const hit = regex.exec(masked);
|
|
53
|
+
if (hit === null) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return [new Violation(
|
|
57
|
+
this.id,
|
|
58
|
+
'warning',
|
|
59
|
+
`weak verb "${hit.groups.verb}" names no concrete action, use a precise verb`,
|
|
60
|
+
new Region(uri, line, hit.groups.lead.length + 1)
|
|
61
|
+
)];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = WeakVerb;
|
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.
|
|
12
|
+
* The default `0.12.0` marks an unreleased build straight from source.
|
|
13
13
|
*/
|
|
14
|
-
const version = '0.
|
|
14
|
+
const version = '0.12.0';
|
|
15
15
|
|
|
16
16
|
module.exports = version;
|