@yegor256/dogent 0.10.0 → 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 +87 -12
- package/package.json +1 -1
- package/src/args.js +18 -3
- package/src/defaults.js +47 -0
- package/src/dogent.js +19 -3
- package/src/openai.js +8 -5
- package/src/prompt.js +0 -4
- package/src/rules/ambiguous-or.js +58 -0
- package/src/rules/conditional.js +55 -0
- package/src/rules/consistent.js +1 -1
- 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/duplicate-section.js +65 -0
- package/src/rules/emoji.js +60 -0
- package/src/rules/example-format.js +32 -0
- package/src/rules/external-link.js +57 -0
- package/src/rules/fence-language.js +55 -0
- package/src/rules/hidden-char.js +61 -0
- package/src/rules/homoglyph.js +82 -0
- package/src/rules/index.js +40 -0
- package/src/rules/inline-code.js +79 -0
- package/src/rules/jargon.js +14 -4
- package/src/rules/meta-reference.js +57 -0
- package/src/rules/placement.js +62 -0
- package/src/rules/quantifier.js +63 -0
- package/src/rules/scope.js +31 -0
- package/src/rules/transition.js +59 -0
- package/src/rules/units.js +81 -0
- package/src/rules/weak-verb.js +62 -0
- package/src/version.js +2 -2
|
@@ -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
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Placement.
|
|
13
|
+
*
|
|
14
|
+
* Transformers attend most to the start and the end of their input and
|
|
15
|
+
* skim the middle, so a critical section buried in the middle third of a
|
|
16
|
+
* manifesto sits exactly where the model is least likely to use it. This
|
|
17
|
+
* standalone check spots a critical section by a heading keyword (Safety,
|
|
18
|
+
* Security, Mission, Critical, Constraints) and warns when it lands in
|
|
19
|
+
* the middle third rather than near the top or bottom. The generic word
|
|
20
|
+
* "Rules" is left out on purpose: it names a neutral section in many
|
|
21
|
+
* manifestos and would misfire. Its prompt hands the deeper judgement to
|
|
22
|
+
* the AI oracle, which weighs which instruction matters most and whether
|
|
23
|
+
* it is well placed.
|
|
24
|
+
*/
|
|
25
|
+
class Placement {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.id = 'placement';
|
|
28
|
+
this.keyword = /^#{1,6}\s+.*\b(?:safety|security|mission|critical|constraints?)\b/iu;
|
|
29
|
+
}
|
|
30
|
+
prompt() {
|
|
31
|
+
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
|
+
}
|
|
33
|
+
violations(document) {
|
|
34
|
+
const uri = document.uri();
|
|
35
|
+
const total = document.text().split('\n').length;
|
|
36
|
+
return document.walk({
|
|
37
|
+
header: (text, row) => this.check(text, row, total, uri),
|
|
38
|
+
prose: () => [],
|
|
39
|
+
snippet: () => [],
|
|
40
|
+
bullets: () => [],
|
|
41
|
+
frontmatter: () => []
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
check(text, row, total, uri) {
|
|
45
|
+
if (!this.keyword.test(text)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const ratio = row / total;
|
|
49
|
+
if (ratio <= 1 / 3 || ratio >= 2 / 3) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const name = text.replace(/^#+\s*/u, '').trim();
|
|
53
|
+
return [new Violation(
|
|
54
|
+
this.id,
|
|
55
|
+
'warning',
|
|
56
|
+
`critical section "${name}" is buried, move it to the top or bottom`,
|
|
57
|
+
new Region(uri, row, 1)
|
|
58
|
+
)];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = Placement;
|
|
@@ -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
|
+
* Quantifier.
|
|
14
|
+
*
|
|
15
|
+
* Flags vague quantity words that leave the count to the agent: "some",
|
|
16
|
+
* "several", "a few", "many", "multiple", and the like. Models track
|
|
17
|
+
* exact quantifiers well yet diverge from human intent on vague ones,
|
|
18
|
+
* whose meaning is an underspecified distribution rather than a number,
|
|
19
|
+
* so a command manifesto should state the exact count or threshold. The
|
|
20
|
+
* list is kept apart from the vague qualifiers so the two rules never
|
|
21
|
+
* double-report. Its prompt hands implicit vagueness, where no listed
|
|
22
|
+
* word appears, to the AI oracle.
|
|
23
|
+
*/
|
|
24
|
+
class Quantifier {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.id = 'quantifier';
|
|
27
|
+
}
|
|
28
|
+
prompt() {
|
|
29
|
+
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
|
+
}
|
|
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 found = [];
|
|
43
|
+
const regex = new RegExp(
|
|
44
|
+
'\\b(?:some|several|a few|a couple|many|multiple|various|' +
|
|
45
|
+
'numerous|a lot of|plenty of)\\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 quantity "${hit[0]}", state an exact number or threshold`,
|
|
55
|
+
new Region(uri, line, hit.index + 1)
|
|
56
|
+
));
|
|
57
|
+
hit = regex.exec(masked);
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = Quantifier;
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
prompt() {
|
|
24
|
+
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
|
+
}
|
|
26
|
+
violations() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = Scope;
|
|
@@ -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,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;
|