@yegor256/dogent 0.12.0 → 0.12.2
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 +2 -2
- package/package.json +1 -1
- package/src/defaults.js +6 -1
- package/src/oracle.js +8 -2
- package/src/rules/command.js +22 -10
- package/src/rules/dead-import.js +1 -1
- package/src/rules/jargon.js +6 -3
- package/src/rules/positive.js +18 -2
- package/src/version.js +2 -2
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Most rewrite prompts for you or score a file, while we enforce
|
|
|
53
53
|
Run it on any manifesto file, no installation required:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
npx @yegor256/dogent@0.
|
|
56
|
+
npx @yegor256/dogent@0.12.1 SKILL.md
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Point it at a directory to lint the default manifestos it holds
|
|
@@ -266,7 +266,7 @@ Reference `dogent` as a remote hook in `.pre-commit-config.yaml`:
|
|
|
266
266
|
```yaml
|
|
267
267
|
repos:
|
|
268
268
|
- repo: https://github.com/yegor256/dogent
|
|
269
|
-
rev: 0.
|
|
269
|
+
rev: 0.12.1
|
|
270
270
|
hooks:
|
|
271
271
|
- id: dogent
|
|
272
272
|
```
|
package/package.json
CHANGED
package/src/defaults.js
CHANGED
|
@@ -40,7 +40,12 @@ class Defaults {
|
|
|
40
40
|
.split('\n')
|
|
41
41
|
.map((line) => line.trim())
|
|
42
42
|
.filter((line) => line !== '' && !line.startsWith('#'))
|
|
43
|
-
.flatMap((line) =>
|
|
43
|
+
.flatMap((line) => {
|
|
44
|
+
const separator = line.search(/\s/u);
|
|
45
|
+
return separator < 0
|
|
46
|
+
? [line]
|
|
47
|
+
: [line.slice(0, separator), line.slice(separator).trimStart()];
|
|
48
|
+
});
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
|
package/src/oracle.js
CHANGED
|
@@ -14,7 +14,9 @@ const Answer = require('./answer');
|
|
|
14
14
|
* The AI second opinion. Wraps the rules and a chat endpoint, builds one
|
|
15
15
|
* prompt from a document, asks the endpoint, and parses the reply into
|
|
16
16
|
* violations paired with the token usage the model reported. Mirrors a
|
|
17
|
-
* rule, but consults a model instead of guessing.
|
|
17
|
+
* rule, but consults a model instead of guessing. Lets each rule veto an
|
|
18
|
+
* oracle flag it knows to be false, so a deterministic guard overrides
|
|
19
|
+
* the model.
|
|
18
20
|
*/
|
|
19
21
|
class Oracle {
|
|
20
22
|
constructor(rules, chat) {
|
|
@@ -24,7 +26,11 @@ class Oracle {
|
|
|
24
26
|
async violations(document) {
|
|
25
27
|
const reply = await this.chat.answer(new Prompt(this.rules, document).text());
|
|
26
28
|
return {
|
|
27
|
-
found: new Answer(reply.content).violations()
|
|
29
|
+
found: new Answer(reply.content).violations().filter(
|
|
30
|
+
(violation) => !this.rules.some(
|
|
31
|
+
(rule) => rule.suppress?.(violation, document)
|
|
32
|
+
)
|
|
33
|
+
),
|
|
28
34
|
usage: reply.usage
|
|
29
35
|
};
|
|
30
36
|
}
|
package/src/rules/command.js
CHANGED
|
@@ -15,6 +15,8 @@ const Region = require('../region');
|
|
|
15
15
|
* checker can only guess: it flags lines that open with a pronoun or
|
|
16
16
|
* end with a question mark, both signs of description, not order. Its
|
|
17
17
|
* prompt hands the subtler imperative-mood judgement to the AI oracle.
|
|
18
|
+
* A deterministic guard then drops any oracle flag on a line that
|
|
19
|
+
* shows neither sign, so a base-form imperative is never flagged.
|
|
18
20
|
*/
|
|
19
21
|
class Command {
|
|
20
22
|
constructor() {
|
|
@@ -37,16 +39,7 @@ class Command {
|
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
judge(text, line, uri) {
|
|
40
|
-
|
|
41
|
-
if (clean === '') {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
const first = clean
|
|
45
|
-
.split(/\s+/u)[0]
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
.replace(/[^a-z]/gu, '');
|
|
48
|
-
const weak = /^(?:i|you|we|they|he|she|it|this|that|these|those|there|here)$/u;
|
|
49
|
-
if (!weak.test(first) && clean.slice(-1) !== '?') {
|
|
42
|
+
if (!this.describes(text)) {
|
|
50
43
|
return [];
|
|
51
44
|
}
|
|
52
45
|
return [new Violation(
|
|
@@ -56,6 +49,25 @@ class Command {
|
|
|
56
49
|
new Region(uri, line, 1)
|
|
57
50
|
)];
|
|
58
51
|
}
|
|
52
|
+
suppress(violation, document) {
|
|
53
|
+
if (violation.rule !== this.id) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const lines = document.text().split('\n');
|
|
57
|
+
return !this.describes(lines[violation.spot.line() - 1] || '');
|
|
58
|
+
}
|
|
59
|
+
describes(text) {
|
|
60
|
+
const clean = text.replace(/^\s*(?:[-*+]|\d+\.)\s+/u, '').trim();
|
|
61
|
+
if (clean === '') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const first = clean
|
|
65
|
+
.split(/\s+/u)[0]
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(/[^a-z]/gu, '');
|
|
68
|
+
const weak = /^(?:i|you|we|they|he|she|it|this|that|these|those|there|here)$/u;
|
|
69
|
+
return weak.test(first) || clean.slice(-1) === '?';
|
|
70
|
+
}
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
module.exports = Command;
|
package/src/rules/dead-import.js
CHANGED
|
@@ -61,7 +61,7 @@ class DeadImport {
|
|
|
61
61
|
return 'Fix or remove the @path import so it points to a real file, and break any circular or overly deep import chain the host tool cannot resolve.';
|
|
62
62
|
}
|
|
63
63
|
prompt() {
|
|
64
|
-
return `${this.id}: flag any @path/to/file import that points to no file on disk`;
|
|
64
|
+
return `${this.id}: flag any @path/to/file import that points to no file on disk; only an @-prefixed token counts as an import, so never treat a bare path in prose as one`;
|
|
65
65
|
}
|
|
66
66
|
violations(document) {
|
|
67
67
|
const uri = document.uri();
|
package/src/rules/jargon.js
CHANGED
|
@@ -48,6 +48,8 @@ const defined = (masked) => {
|
|
|
48
48
|
while (hit !== null) {
|
|
49
49
|
if (hit.groups.acronym) {
|
|
50
50
|
found.add(hit.groups.acronym);
|
|
51
|
+
} else if (/^[A-Z]{2,}$/u.test(hit.groups.gloss)) {
|
|
52
|
+
found.add(hit.groups.gloss);
|
|
51
53
|
} else {
|
|
52
54
|
found.add(initials(hit.groups.gloss));
|
|
53
55
|
}
|
|
@@ -64,10 +66,11 @@ const undefining = (acronym, scope) => !scope.known.has(acronym) &&
|
|
|
64
66
|
*
|
|
65
67
|
* Flags an acronym that lands in prose without ever being expanded. An
|
|
66
68
|
* acronym counts as defined when the document, anywhere, follows it with
|
|
67
|
-
* a parenthetical gloss, as in "RBAC (role-based access control)",
|
|
69
|
+
* a parenthetical gloss, as in "RBAC (role-based access control)", when
|
|
68
70
|
* a parenthetical's word initials spell it, as in "AAA pattern
|
|
69
|
-
* (Arrange-Act-Assert)",
|
|
70
|
-
*
|
|
71
|
+
* (Arrange-Act-Assert)", or when the expansion precedes a parenthetical
|
|
72
|
+
* acronym, as in "Virtual Private Network (VPN)", so a single expansion
|
|
73
|
+
* licenses every later mention. Well-known acronyms sit
|
|
71
74
|
* in a built-in allowlist and pass untouched. Only the first unexpanded
|
|
72
75
|
* occurrence of each acronym is reported. Its prompt hands non-acronym
|
|
73
76
|
* domain jargon, the rare nouns a reader cannot parse, to the AI oracle.
|
package/src/rules/positive.js
CHANGED
|
@@ -19,7 +19,12 @@ const mask = require('../mask');
|
|
|
19
19
|
* "Only use real data" beats "Don't use mock data". Its prompt hands
|
|
20
20
|
* subtler bans, those carrying no head keyword, to the AI
|
|
21
21
|
* oracle, which rewrites a prohibition with no keyword as a positive
|
|
22
|
-
* command.
|
|
22
|
+
* command. The prompt demands an actual negation before flagging, so
|
|
23
|
+
* an affirmative imperative that already states what to do stays clean.
|
|
24
|
+
* Because the model still misreads plain imperatives as bans, a
|
|
25
|
+
* deterministic guard then drops any oracle flag on a line that carries
|
|
26
|
+
* no negation token at all, so an affirmative imperative can never be
|
|
27
|
+
* reported regardless of what the model returns.
|
|
23
28
|
*/
|
|
24
29
|
class Positive {
|
|
25
30
|
constructor() {
|
|
@@ -29,7 +34,7 @@ class Positive {
|
|
|
29
34
|
return 'Rewrite a prohibition as a positive imperative stating what to do, since a ban forces the model to process the forbidden idea first.';
|
|
30
35
|
}
|
|
31
36
|
prompt() {
|
|
32
|
-
return `${this.id}: flag
|
|
37
|
+
return `${this.id}: flag an instruction only when it forbids or negates an action, including a ban that carries no fixed keyword, and rewrite each as a positive imperative; leave an affirmative imperative that already states what to do untouched, returning nothing for it`;
|
|
33
38
|
}
|
|
34
39
|
violations(document) {
|
|
35
40
|
const uri = document.uri();
|
|
@@ -41,6 +46,17 @@ class Positive {
|
|
|
41
46
|
frontmatter: () => []
|
|
42
47
|
});
|
|
43
48
|
}
|
|
49
|
+
suppress(violation, document) {
|
|
50
|
+
if (violation.rule !== this.id) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const lines = document.text().split('\n');
|
|
54
|
+
return !this.negated(lines[violation.spot.line() - 1] || '');
|
|
55
|
+
}
|
|
56
|
+
negated(text) {
|
|
57
|
+
const regex = /\b(?:do not|don't|never|avoid|refrain from|must not|no longer|no|not)\b/iu;
|
|
58
|
+
return regex.test(mask(text));
|
|
59
|
+
}
|
|
44
60
|
scan(text, line, uri) {
|
|
45
61
|
const regex = /^(?<marker>\s*(?:[-*+]|\d+\.)\s+)?(?:do not|don't|never|avoid|refrain from|must not|no longer)\b/iu;
|
|
46
62
|
const hit = regex.exec(mask(text));
|
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.
|
|
12
|
+
* The default `0.12.2` marks an unreleased build straight from source.
|
|
13
13
|
*/
|
|
14
|
-
const version = '0.12.
|
|
14
|
+
const version = '0.12.2';
|
|
15
15
|
|
|
16
16
|
module.exports = version;
|