@gessobuild/anti-slop 0.4.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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/commands/critique.md +67 -0
- package/dist/cli/install.d.ts +10 -0
- package/dist/cli/install.js +65 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.js +46 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +130 -0
- package/dist/engine.d.ts +22 -0
- package/dist/engine.js +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +4046 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +11 -0
- package/package.json +59 -0
- package/skills/anti-slop/SKILL.md +322 -0
- package/skills/anti-slop/references/rules.md +1650 -0
package/dist/engine.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Anti-slop: engine.
|
|
2
|
+
//
|
|
3
|
+
// Three generic operations over a rule registry:
|
|
4
|
+
// - runSlopGuard: detect -> { pass, issues, severity, counts }
|
|
5
|
+
// - applySlopFixes: run every FIX/BASE rule's deterministic rewrite
|
|
6
|
+
// - buildSlopConstraintsBlock: render the prevention text for a prompt
|
|
7
|
+
//
|
|
8
|
+
// Every rule call is wrapped in try/catch: a buggy regex must never break a
|
|
9
|
+
// live generation; it degrades to "no hits" / "no fix".
|
|
10
|
+
/** Per-rule severity is capped so one runaway pattern can't dwarf the others. */
|
|
11
|
+
export const PER_RULE_SEVERITY_CAP = 4;
|
|
12
|
+
// Hit details quote evidence from the scanned document, which is untrusted
|
|
13
|
+
// input that ends up embedded in reports, agent context, and retry prompts.
|
|
14
|
+
// Bound what an author of a hostile document can smuggle through: collapse
|
|
15
|
+
// control characters and newlines, and cap the excerpt length.
|
|
16
|
+
const DETAIL_MAX_LENGTH = 80;
|
|
17
|
+
function sanitizeDetail(detail) {
|
|
18
|
+
const flat = detail
|
|
19
|
+
.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, " ")
|
|
20
|
+
.replace(/\s{2,}/g, " ")
|
|
21
|
+
.trim();
|
|
22
|
+
return flat.length > DETAIL_MAX_LENGTH
|
|
23
|
+
? `${flat.slice(0, DETAIL_MAX_LENGTH)}...`
|
|
24
|
+
: flat;
|
|
25
|
+
}
|
|
26
|
+
function isSanctioned(rule, ctx) {
|
|
27
|
+
try {
|
|
28
|
+
return rule.sanctionedBy?.(ctx.styleRef, ctx) ?? false;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function activeRules(ctx, rules) {
|
|
35
|
+
return rules.filter((r) => !isSanctioned(r, ctx));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Detect slop in generated HTML. Severity-scored for a retry comparator;
|
|
39
|
+
* style-aware via each rule's sanctionedBy().
|
|
40
|
+
*/
|
|
41
|
+
export function runSlopGuard(html, ctx, rules) {
|
|
42
|
+
const issues = [];
|
|
43
|
+
const byRule = {};
|
|
44
|
+
let severity = 0;
|
|
45
|
+
let gatingTotal = 0;
|
|
46
|
+
for (const rule of activeRules(ctx, rules)) {
|
|
47
|
+
// "base" rules inject a base-style default; their absence is not a defect,
|
|
48
|
+
// so they never count toward pass/severity/issues (only applySlopFixes
|
|
49
|
+
// consumes their detect()).
|
|
50
|
+
if (rule.tier === "base")
|
|
51
|
+
continue;
|
|
52
|
+
let hits;
|
|
53
|
+
try {
|
|
54
|
+
hits = rule.detect(html, ctx);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
hits = [];
|
|
58
|
+
}
|
|
59
|
+
if (hits.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
byRule[rule.id] = hits.length;
|
|
62
|
+
// "flag" rules are advisory by contract (see SlopTier): they are reported
|
|
63
|
+
// in issues and counted in byRule for telemetry, but they never gate the
|
|
64
|
+
// verdict or feed the retry comparator's severity.
|
|
65
|
+
const advisory = rule.tier === "flag";
|
|
66
|
+
if (!advisory) {
|
|
67
|
+
gatingTotal += hits.length;
|
|
68
|
+
severity += Math.min(PER_RULE_SEVERITY_CAP, hits.length * rule.severity);
|
|
69
|
+
}
|
|
70
|
+
const examples = hits.slice(0, 3).map((h) => sanitizeDetail(h.detail));
|
|
71
|
+
issues.push(`[${rule.category}/${rule.id}]${advisory ? " [advisory]" : ""} ${hits.length}x: ${rule.tell}` +
|
|
72
|
+
(examples.length > 0 ? ` (e.g. ${examples.join("; ")})` : ""));
|
|
73
|
+
}
|
|
74
|
+
const total = Object.values(byRule).reduce((a, b) => a + b, 0);
|
|
75
|
+
return { pass: gatingTotal === 0, issues, severity, counts: { byRule, total } };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Apply every FIX/BASE-tier rule's deterministic rewrite. Idempotent:
|
|
79
|
+
* re-running on already-clean HTML is a no-op. A fixer that throws leaves
|
|
80
|
+
* the HTML untouched (a guard must never break generation).
|
|
81
|
+
*/
|
|
82
|
+
export function applySlopFixes(html, ctx, rules) {
|
|
83
|
+
let working = html;
|
|
84
|
+
const fixes = {};
|
|
85
|
+
for (const rule of activeRules(ctx, rules)) {
|
|
86
|
+
if ((rule.tier !== "fix" && rule.tier !== "base") || !rule.fix)
|
|
87
|
+
continue;
|
|
88
|
+
let before = 0;
|
|
89
|
+
try {
|
|
90
|
+
before = rule.detect(working, ctx).length;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
before = 0;
|
|
94
|
+
}
|
|
95
|
+
if (before === 0)
|
|
96
|
+
continue;
|
|
97
|
+
try {
|
|
98
|
+
const next = rule.fix(working, ctx);
|
|
99
|
+
if (next !== working) {
|
|
100
|
+
working = next;
|
|
101
|
+
fixes[rule.id] = before;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Leave the HTML as-is on any fixer error.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const total = Object.values(fixes).reduce((a, b) => a + b, 0);
|
|
109
|
+
return { html: working, fixes, total };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Render the prevention block for a system prompt. Drops rules the active
|
|
113
|
+
* style legitimately sanctions, so the prompt never contradicts the style
|
|
114
|
+
* reference.
|
|
115
|
+
*/
|
|
116
|
+
export function buildSlopConstraintsBlock(styleRef, rules) {
|
|
117
|
+
const active = rules.filter((r) => !isSanctioned(r, { styleRef }));
|
|
118
|
+
const byCat = new Map();
|
|
119
|
+
for (const r of active) {
|
|
120
|
+
const arr = byCat.get(r.category) ?? [];
|
|
121
|
+
arr.push(`- ${r.prevention}`);
|
|
122
|
+
byCat.set(r.category, arr);
|
|
123
|
+
}
|
|
124
|
+
const sections = [...byCat.entries()].map(([cat, lines]) => `${cat.toUpperCase()}:\n${lines.join("\n")}`);
|
|
125
|
+
return [
|
|
126
|
+
"AI-SLOP GUARD (these patterns are auto-detected/auto-fixed after generation, avoid them up front):",
|
|
127
|
+
...sections,
|
|
128
|
+
].join("\n\n");
|
|
129
|
+
}
|
|
130
|
+
/** Corrective message for a retry path. */
|
|
131
|
+
export function buildSlopCorrectionPrompt(check) {
|
|
132
|
+
return [
|
|
133
|
+
"Your previous output tripped the AI-slop guard:",
|
|
134
|
+
...check.issues.map((i) => `- ${i}`),
|
|
135
|
+
"",
|
|
136
|
+
"Re-emit the screen with these patterns removed. Keep every other design decision intact.",
|
|
137
|
+
].join("\n");
|
|
138
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { applySlopFixes, buildSlopConstraintsBlock, buildSlopCorrectionPrompt, PER_RULE_SEVERITY_CAP, runSlopGuard, } from "./engine.js";
|
|
2
|
+
export { FLAGSHIP_RULES } from "./rules.js";
|
|
3
|
+
export type { SlopCategory, SlopCheck, SlopCtx, SlopCtxLike, SlopFixResult, SlopHit, SlopRule, SlopStyleHints, SlopTier, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @gessobuild/anti-slop: deterministic AI-slop detection and fixing for
|
|
2
|
+
// generated HTML/CSS.
|
|
3
|
+
//
|
|
4
|
+
// import { runSlopGuard, applySlopFixes, FLAGSHIP_RULES } from "@gessobuild/anti-slop"
|
|
5
|
+
//
|
|
6
|
+
// const check = runSlopGuard(html, {}, FLAGSHIP_RULES) // detect
|
|
7
|
+
// const fixed = applySlopFixes(html, {}, FLAGSHIP_RULES) // rewrite
|
|
8
|
+
//
|
|
9
|
+
// The engine is generic over the rule context, so hosts can register their
|
|
10
|
+
// own rules with richer style/token types alongside (or instead of) the
|
|
11
|
+
// flagship registry.
|
|
12
|
+
export { applySlopFixes, buildSlopConstraintsBlock, buildSlopCorrectionPrompt, PER_RULE_SEVERITY_CAP, runSlopGuard, } from "./engine.js";
|
|
13
|
+
export { FLAGSHIP_RULES } from "./rules.js";
|
package/dist/rules.d.ts
ADDED