@yegor256/dogent 0.12.2 → 0.12.4
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/package.json +1 -1
- package/src/dogent.js +8 -2
- package/src/rules/description-triggers.js +28 -1
- package/src/rules/description-voice.js +23 -5
- package/src/rules/no-articles.js +15 -0
- package/src/rules/untrusted.js +1 -1
- package/src/version.js +2 -2
package/package.json
CHANGED
package/src/dogent.js
CHANGED
|
@@ -73,11 +73,17 @@ const scan = () => {
|
|
|
73
73
|
return [];
|
|
74
74
|
};
|
|
75
75
|
const scanned = scan();
|
|
76
|
-
scanned.
|
|
76
|
+
const bodies = new Map(scanned.map((file) => [file, fs.readFileSync(file, 'utf8')]));
|
|
77
|
+
scanned.forEach((file) => {
|
|
78
|
+
const body = bodies.get(file);
|
|
79
|
+
const lines = body === '' ? 0 : body.split('\n').length - (body.endsWith('\n') ? 1 : 0);
|
|
80
|
+
const bytes = Buffer.byteLength(body);
|
|
81
|
+
process.stderr.write(`Scanning ${file} (${lines} lines, ${bytes} bytes)\n`);
|
|
82
|
+
});
|
|
77
83
|
const checks = rules();
|
|
78
84
|
process.stderr.write(`${scanned.length} files scanned, ${checks.length} rules applied\n`);
|
|
79
85
|
const documents = scanned.map(
|
|
80
|
-
(file) => new Markdown(file,
|
|
86
|
+
(file) => new Markdown(file, bodies.get(file)).document()
|
|
81
87
|
);
|
|
82
88
|
const started = Date.now();
|
|
83
89
|
const suppressed = args.suppress();
|
|
@@ -16,6 +16,9 @@ const Region = require('../region');
|
|
|
16
16
|
* short or that never names a trigger with the word "when". Its prompt
|
|
17
17
|
* hands the deeper judgement to the AI oracle, which weighs whether the
|
|
18
18
|
* description truly names the situations and phrases that activate it.
|
|
19
|
+
* A quoted example phrase is the strongest form of that naming, so a
|
|
20
|
+
* deterministic guard then vetoes any oracle flag on a description that
|
|
21
|
+
* holds both "when" and a quoted phrase, however the model rules.
|
|
19
22
|
*/
|
|
20
23
|
class DescriptionTriggers {
|
|
21
24
|
constructor() {
|
|
@@ -26,7 +29,7 @@ class DescriptionTriggers {
|
|
|
26
29
|
return 'Name the concrete situations and user phrases that should activate the skill in its description, so the loader knows exactly when to invoke it.';
|
|
27
30
|
}
|
|
28
31
|
prompt() {
|
|
29
|
-
return `${this.id}: in a SKILL.md, flag a description that is too short or fails to name the concrete situations and user phrases that should activate the skill, even when it contains the word "when"`;
|
|
32
|
+
return `${this.id}: in a SKILL.md, flag a description that is too short or fails to name the concrete situations and user phrases that should activate the skill, even when it contains the word "when"; a description that quotes an example user phrase, such as "file this bug", already names a trigger in its strongest form and must never be flagged`;
|
|
30
33
|
}
|
|
31
34
|
violations(document) {
|
|
32
35
|
const uri = document.uri();
|
|
@@ -56,6 +59,30 @@ class DescriptionTriggers {
|
|
|
56
59
|
}
|
|
57
60
|
return [];
|
|
58
61
|
}
|
|
62
|
+
suppress(violation, document) {
|
|
63
|
+
if (violation.rule !== this.id) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const value = this.description(document);
|
|
67
|
+
return /\bwhen\b/iu.test(value) && this.quoted(value);
|
|
68
|
+
}
|
|
69
|
+
description(document) {
|
|
70
|
+
const pairs = document.walk({
|
|
71
|
+
header: () => [],
|
|
72
|
+
prose: () => [],
|
|
73
|
+
snippet: () => [],
|
|
74
|
+
bullets: () => [],
|
|
75
|
+
frontmatter: (keys) => keys
|
|
76
|
+
});
|
|
77
|
+
const found = pairs.filter((pair) => pair.key === 'description');
|
|
78
|
+
if (found.length === 0) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
return found[0].value.trim();
|
|
82
|
+
}
|
|
83
|
+
quoted(value) {
|
|
84
|
+
return /["'‘’“”][^"'‘’“”]+["'‘’“”]/u.test(value);
|
|
85
|
+
}
|
|
59
86
|
flag(message, row, uri) {
|
|
60
87
|
return new Violation(this.id, 'warning', message, new Region(uri, row, 1));
|
|
61
88
|
}
|
|
@@ -20,7 +20,11 @@ const Region = require('../region');
|
|
|
20
20
|
* Distinct from description-triggers, which checks that a "when" clause
|
|
21
21
|
* exists, and from description-length, which checks the size; this one
|
|
22
22
|
* checks the grammatical voice. Its prompt hands subtler voice
|
|
23
|
-
* judgement to the AI oracle.
|
|
23
|
+
* judgement to the AI oracle. A deterministic guard then drops any
|
|
24
|
+
* oracle flag that lands off the frontmatter description row, or on that
|
|
25
|
+
* row when the deterministic pronoun check finds nothing, so the oracle
|
|
26
|
+
* stays bound to the same single line and vocabulary the standalone
|
|
27
|
+
* checker enforces.
|
|
24
28
|
*/
|
|
25
29
|
class DescriptionVoice {
|
|
26
30
|
constructor() {
|
|
@@ -38,6 +42,23 @@ class DescriptionVoice {
|
|
|
38
42
|
if (uri.replace(/^.*\//u, '') !== 'SKILL.md') {
|
|
39
43
|
return [];
|
|
40
44
|
}
|
|
45
|
+
const pair = this.description(document);
|
|
46
|
+
if (pair === null) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return this.judge(pair, uri);
|
|
50
|
+
}
|
|
51
|
+
suppress(violation, document) {
|
|
52
|
+
if (violation.rule !== this.id) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const pair = this.description(document);
|
|
56
|
+
if (pair === null || violation.spot.line() !== pair.row) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return this.judge(pair, document.uri()).length === 0;
|
|
60
|
+
}
|
|
61
|
+
description(document) {
|
|
41
62
|
const pairs = document.walk({
|
|
42
63
|
header: () => [],
|
|
43
64
|
prose: () => [],
|
|
@@ -46,10 +67,7 @@ class DescriptionVoice {
|
|
|
46
67
|
frontmatter: (keys) => keys
|
|
47
68
|
});
|
|
48
69
|
const found = pairs.filter((pair) => pair.key === 'description');
|
|
49
|
-
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
return this.judge(found[0], uri);
|
|
70
|
+
return found.length === 0 ? null : found[0];
|
|
53
71
|
}
|
|
54
72
|
judge(pair, uri) {
|
|
55
73
|
const text = pair.value.replace(/use when.*$/isu, '');
|
package/src/rules/no-articles.js
CHANGED
|
@@ -35,6 +35,21 @@ class NoArticles {
|
|
|
35
35
|
frontmatter: () => []
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
suppress(violation, document) {
|
|
39
|
+
if (violation.rule !== this.id) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return this.headers(document).has(violation.spot.line());
|
|
43
|
+
}
|
|
44
|
+
headers(document) {
|
|
45
|
+
return new Set(document.walk({
|
|
46
|
+
header: (text, line) => [line],
|
|
47
|
+
prose: () => [],
|
|
48
|
+
snippet: () => [],
|
|
49
|
+
bullets: () => [],
|
|
50
|
+
frontmatter: () => []
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
38
53
|
scan(text, line, uri) {
|
|
39
54
|
const found = [];
|
|
40
55
|
const masked = mask(text);
|
package/src/rules/untrusted.js
CHANGED
|
@@ -44,7 +44,7 @@ class Untrusted {
|
|
|
44
44
|
}
|
|
45
45
|
scan(text, line, uri) {
|
|
46
46
|
const masked = mask(text);
|
|
47
|
-
const verb =
|
|
47
|
+
const verb = /(?<!-)\b(?:read|fetch|open|follow|execute)\b(?!-)/iu;
|
|
48
48
|
const source = /\b(?:page|url|link|email|file|issue|output|comment)\b/iu;
|
|
49
49
|
const guard = /\b(?:as data|do not follow|treat as untrusted|inside delimiters|untrusted)\b/iu;
|
|
50
50
|
if (!verb.test(masked) || !source.test(masked) || guard.test(masked)) {
|
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.4` marks an unreleased build straight from source.
|
|
13
13
|
*/
|
|
14
|
-
const version = '0.12.
|
|
14
|
+
const version = '0.12.4';
|
|
15
15
|
|
|
16
16
|
module.exports = version;
|