@event4u/agent-config 7.3.0 → 7.5.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/.claude-plugin/marketplace.json +4 -1
- package/CHANGELOG.md +81 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/fix/pr-comments.md +42 -13
- package/dist/agent-src/commands/review-changes.md +14 -7
- package/dist/agent-src/rules/artifact-engagement-recording.md +9 -0
- package/dist/agent-src/rules/cli-output-handling.md +18 -0
- package/dist/agent-src/rules/domain-safety-pii.md +4 -3
- package/dist/agent-src/rules/icon-consistency.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +2 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +8 -5
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -1
- package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +4 -0
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +2 -1
- package/dist/agent-src/skills/code-review/SKILL.md +4 -0
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/design-intelligence/SKILL.md +40 -0
- package/dist/agent-src/skills/design-review/SKILL.md +23 -11
- package/dist/agent-src/skills/design-system-capture/SKILL.md +41 -0
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +62 -0
- package/dist/agent-src/skills/design-tokens/SKILL.md +1 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +6 -0
- package/dist/agent-src/skills/flux/SKILL.md +4 -0
- package/dist/agent-src/skills/iconography/SKILL.md +1 -0
- package/dist/agent-src/skills/image-editing/evals/triggers.json +13 -13
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +136 -0
- package/dist/agent-src/skills/judge-artifact-completeness/calibration/fixtures.json +149 -0
- package/dist/agent-src/skills/judge-artifact-completeness/evals/triggers.json +17 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/architecture-score.json +73 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/pr-review-score.json +65 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/roadmap-score.json +73 -0
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +73 -0
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +99 -0
- package/dist/agent-src/skills/judge-injection-defense/calibration/fixtures.json +24 -0
- package/dist/agent-src/skills/judge-injection-defense/evals/triggers.json +17 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/behavioral-fixtures.json +63 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/coverage-report.md +53 -0
- package/dist/agent-src/skills/judge-injection-defense/fixtures/perturbation-taxonomy.json +110 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +134 -0
- package/dist/agent-src/skills/judge-synthesis/evals/triggers.json +17 -0
- package/dist/agent-src/skills/logo-generation/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +18 -0
- package/dist/agent-src/skills/react-shadcn-ui/reference/registry.md +59 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +5 -1
- package/dist/agent-src/skills/release-comms/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +4 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/script-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/sql-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +1 -1
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +4 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +109 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +8 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +7 -4
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +6 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +28 -20
- package/docs/command-flows.md +1 -1
- package/docs/enforcement-by-host.md +9 -0
- package/docs/governance-advantage.md +72 -0
- package/docs/guidelines/design-antipatterns.md +26 -0
- package/docs/guidelines/design-canon.md +62 -0
- package/package.json +2 -1
- package/src/config/agent-settings.template.yml +21 -0
- package/src/scripts/_cli/cmd_analyze_session.ts +347 -0
- package/src/scripts/_dispatch.bash +8 -0
- package/src/scripts/_lib/token_count.ts +110 -35
- package/src/scripts/audit_initial_context.ts +37 -12
- package/src/scripts/bench_quality_run.ts +253 -0
- package/src/scripts/bench_rule_load_latency.ts +178 -0
- package/src/scripts/check_kernel_prefix_stability.ts +0 -0
- package/src/scripts/check_quality_regression.ts +244 -0
- package/src/scripts/check_token_quality_golden.ts +253 -0
- package/src/scripts/check_token_regression.ts +266 -0
- package/src/scripts/design_slop_rules.test.ts +193 -0
- package/src/scripts/design_slop_rules.ts +502 -0
- package/src/scripts/external_sources_denylist.json +7 -1
- package/src/scripts/hook_manifest.yaml +26 -3
- package/src/scripts/hooks/design_slop_hook.ts +181 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +228 -0
- package/src/scripts/lint_agent_security.ts +1 -0
- package/src/scripts/lint_confusables.ts +197 -0
- package/src/scripts/lint_design_slop.ts +245 -0
- package/src/scripts/probe_host_compliance.ts +145 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* design_slop_rules — the deterministic anti-slop rule registry.
|
|
4
|
+
*
|
|
5
|
+
* Data + matchers for the aesthetic-PROVENANCE tells that make a UI "look like
|
|
6
|
+
* an AI made it" — the layer `lint_design_quality` deliberately does NOT cover
|
|
7
|
+
* (it owns the OBJECTIVE floors: contrast, font-size, line-length, reduced-motion,
|
|
8
|
+
* heading hierarchy, focus). This registry is the deterministic backing for the
|
|
9
|
+
* PROSE catalog in `docs/guidelines/design-antipatterns.md`; every rule cites its
|
|
10
|
+
* catalog id (V1, C2, …) so prose ↔ rule stay traceable.
|
|
11
|
+
*
|
|
12
|
+
* DESIGN CONSTRAINT (why no postcss/@babel): this package ships to consumers via
|
|
13
|
+
* `npx` and is dependency-free by design (see lint_design_quality header). Heavy
|
|
14
|
+
* parsers (postcss/@babel/tailwind resolver) would bloat the install and break
|
|
15
|
+
* portability. The Phase-0 tells are all detectable by pattern analysis on
|
|
16
|
+
* CSS/HTML/JSX text without a cascade — we follow lint_design_quality's proven
|
|
17
|
+
* dependency-free approach. Council 2026-06-28 assumed those libs were present;
|
|
18
|
+
* for THIS package, pattern-based is the correct, frugal adaptation.
|
|
19
|
+
*
|
|
20
|
+
* Severity is a REBUTTABLE PRESUMPTION, never a hard block:
|
|
21
|
+
* P0 — objective failure (none here; those live in lint_design_quality)
|
|
22
|
+
* P1 — high-confidence tell (e.g. the side-stripe signature)
|
|
23
|
+
* P2 — confident tell
|
|
24
|
+
* P3 — co-occurrence / lower-confidence heuristic
|
|
25
|
+
* A rule is suppressed when the consumer's DESIGN.md declares the pattern as
|
|
26
|
+
* intentional (the context gate), via inline-ignore, or via .design-quality.json.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export type Severity = "P0" | "P1" | "P2" | "P3";
|
|
30
|
+
export type Engine = "css" | "html" | "jsx" | "copy";
|
|
31
|
+
|
|
32
|
+
export interface RawHit {
|
|
33
|
+
line: number;
|
|
34
|
+
snippet: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DesignContext {
|
|
38
|
+
/** lowercased DESIGN.md text, or "" when absent */
|
|
39
|
+
raw: string;
|
|
40
|
+
/** true when DESIGN.md mentions any of the given keywords */
|
|
41
|
+
has(...keywords: string[]): boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RuleInput {
|
|
45
|
+
content: string;
|
|
46
|
+
lines: string[];
|
|
47
|
+
ext: string; // e.g. ".css"
|
|
48
|
+
ctx: DesignContext;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SlopRule {
|
|
52
|
+
id: string; // e.g. "slop-v1-side-stripe"
|
|
53
|
+
catalogId: string; // e.g. "V1"
|
|
54
|
+
severity: Severity;
|
|
55
|
+
engines: Engine[];
|
|
56
|
+
description: string;
|
|
57
|
+
/** human-facing note on what to do — printed with the finding */
|
|
58
|
+
message: string;
|
|
59
|
+
/** suppress the rule entirely for this file when DESIGN.md declares intent */
|
|
60
|
+
gated?: (ctx: DesignContext) => boolean;
|
|
61
|
+
detect: (input: RuleInput) => RawHit[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Shared helpers (pattern-based, dependency-free)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/** Split CSS-ish text into naive `selector { body }` blocks with the line of `{`. */
|
|
69
|
+
export function cssBlocks(content: string): Array<{ selector: string; body: string; line: number }> {
|
|
70
|
+
const blocks: Array<{ selector: string; body: string; line: number }> = [];
|
|
71
|
+
const re = /([^{}]+)\{([^{}]*)\}/g;
|
|
72
|
+
let m: RegExpExecArray | null;
|
|
73
|
+
while ((m = re.exec(content)) !== null) {
|
|
74
|
+
const before = content.slice(0, m.index);
|
|
75
|
+
const line = (before.match(/\n/g) ?? []).length + 1;
|
|
76
|
+
blocks.push({ selector: (m[1] ?? "").trim(), body: m[2] ?? "", line });
|
|
77
|
+
}
|
|
78
|
+
return blocks;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function lineOf(content: string, index: number): number {
|
|
82
|
+
return (content.slice(0, index).match(/\n/g) ?? []).length + 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Visible text from HTML/JSX/Markdown: strip tags, code fences, attributes. */
|
|
86
|
+
export function visibleText(content: string, ext: string): string {
|
|
87
|
+
let t = content;
|
|
88
|
+
if (ext === ".md" || ext === ".mdx") {
|
|
89
|
+
t = t.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]*`/g, " ");
|
|
90
|
+
}
|
|
91
|
+
// strip HTML/JSX tags and JSX expression braces
|
|
92
|
+
t = t.replace(/<[^>]+>/g, " ").replace(/\{[^}]*\}/g, " ");
|
|
93
|
+
return t;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Coarse HSL hue bucket (0–11, 30° each) from a #rgb/#rrggbb hex; null if grayscale/near-black/near-white (not an accent). */
|
|
97
|
+
export function hueBucket(hex: string): number | null {
|
|
98
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
99
|
+
if (!m) return null;
|
|
100
|
+
let h = m[1] ?? "";
|
|
101
|
+
if (h.length === 3) {
|
|
102
|
+
h = h
|
|
103
|
+
.split("")
|
|
104
|
+
.map((c) => c + c)
|
|
105
|
+
.join("");
|
|
106
|
+
}
|
|
107
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
108
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
109
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
110
|
+
const max = Math.max(r, g, b);
|
|
111
|
+
const min = Math.min(r, g, b);
|
|
112
|
+
const d = max - min;
|
|
113
|
+
const l = (max + min) / 2;
|
|
114
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
115
|
+
if (s < 0.25 || l < 0.12 || l > 0.95) return null; // grayscale / near-black / near-white
|
|
116
|
+
let hue = 0;
|
|
117
|
+
if (max === r) hue = ((g - b) / d) % 6;
|
|
118
|
+
else if (max === g) hue = (b - r) / d + 2;
|
|
119
|
+
else hue = (r - g) / d + 4;
|
|
120
|
+
hue *= 60;
|
|
121
|
+
if (hue < 0) hue += 360;
|
|
122
|
+
return Math.floor(hue / 30) % 12;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Approximate `<section>` bodies for layout-cap rules (no recursion; coarse, deterministic). */
|
|
126
|
+
export function sectionBodies(content: string): string[] {
|
|
127
|
+
const starts: number[] = [];
|
|
128
|
+
const re = /<section\b|<Section\b/gi;
|
|
129
|
+
let m: RegExpExecArray | null;
|
|
130
|
+
while ((m = re.exec(content)) !== null) starts.push(m.index);
|
|
131
|
+
if (starts.length === 0) return [];
|
|
132
|
+
const bodies: string[] = [];
|
|
133
|
+
for (let i = 0; i < starts.length; i++) {
|
|
134
|
+
const end = i + 1 < starts.length ? starts[i + 1]! : content.length;
|
|
135
|
+
bodies.push(content.slice(starts[i]!, end));
|
|
136
|
+
}
|
|
137
|
+
return bodies;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Coarse structural signature of a section body (for monotony / zigzag caps). */
|
|
141
|
+
function sectionSignature(body: string): { sig: string; mediaText: boolean } {
|
|
142
|
+
const img = /<img\b|<picture\b|<Image\b|background-image/i.test(body);
|
|
143
|
+
const grid = /\b(flex|grid|grid-cols|col-span|md:flex|lg:grid)\b/i.test(body);
|
|
144
|
+
const heading = /<h[1-6]\b/i.test(body);
|
|
145
|
+
const text = heading || /<(p|li)\b/i.test(body);
|
|
146
|
+
const blocks = Math.min((body.match(/<div\b/gi) ?? []).length, 5);
|
|
147
|
+
return { sig: `${img ? "I" : ""}${grid ? "G" : ""}${heading ? "H" : ""}${blocks}`, mediaText: img && text && grid };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const DEFAULT_FONTS = [
|
|
151
|
+
"inter",
|
|
152
|
+
"roboto",
|
|
153
|
+
"dm sans",
|
|
154
|
+
"geist",
|
|
155
|
+
"space grotesk",
|
|
156
|
+
"instrument serif",
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const BUZZWORDS = [
|
|
160
|
+
"streamline",
|
|
161
|
+
"empower",
|
|
162
|
+
"supercharge",
|
|
163
|
+
"world-class",
|
|
164
|
+
"enterprise-grade",
|
|
165
|
+
"seamlessly",
|
|
166
|
+
"robust",
|
|
167
|
+
"leverage",
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// The registry
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
export const SLOP_RULES: SlopRule[] = [
|
|
175
|
+
{
|
|
176
|
+
id: "slop-v1-side-stripe",
|
|
177
|
+
catalogId: "V1",
|
|
178
|
+
severity: "P1",
|
|
179
|
+
engines: ["css"],
|
|
180
|
+
description: "Colored side-stripe border (border-left/right > 1px) — the #1 AI-UI signature",
|
|
181
|
+
message:
|
|
182
|
+
"Side-stripe accent border reads as scaffold-default. Remove it, or declare an intentional stripe in DESIGN.md (V1).",
|
|
183
|
+
gated: (ctx) => ctx.has("side stripe", "side-stripe", "stripe accent", "accent border"),
|
|
184
|
+
detect: ({ content }) =>
|
|
185
|
+
cssBlocks(content)
|
|
186
|
+
// a side-stripe is a left/right border with a width > 1px and a color;
|
|
187
|
+
// exclude semantic blockquote/quote selectors (legitimate left rule)
|
|
188
|
+
.filter((b) => !/blockquote|\bquote\b|<q>/i.test(b.selector))
|
|
189
|
+
.filter((b) =>
|
|
190
|
+
/border-(?:left|right)\s*:\s*(?:[2-9]|\d{2,})px\s+\w+\s+(?:#|rgb|hsl|oklch|var\(|[a-z])/i.test(
|
|
191
|
+
b.body,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
.map((b) => ({ line: b.line, snippet: b.selector.slice(0, 80) })),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "slop-v3-ghost-card",
|
|
198
|
+
catalogId: "V3",
|
|
199
|
+
severity: "P2",
|
|
200
|
+
engines: ["css"],
|
|
201
|
+
description: "Ghost card: 1px border AND a ≥16px box-shadow on the same surface",
|
|
202
|
+
message:
|
|
203
|
+
"Border + heavy shadow together signal no confident depth decision. Choose one (V3).",
|
|
204
|
+
detect: ({ content }) =>
|
|
205
|
+
cssBlocks(content)
|
|
206
|
+
.filter(
|
|
207
|
+
(b) =>
|
|
208
|
+
/border\s*:\s*1px\s+solid/i.test(b.body) &&
|
|
209
|
+
/box-shadow\s*:[^;]*?(?:1[6-9]|[2-9]\d)px/i.test(b.body),
|
|
210
|
+
)
|
|
211
|
+
.map((b) => ({ line: b.line, snippet: b.selector.slice(0, 80) })),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "slop-v6-diagonal-stripes",
|
|
215
|
+
catalogId: "V6",
|
|
216
|
+
severity: "P2",
|
|
217
|
+
engines: ["css"],
|
|
218
|
+
description: "repeating-linear-gradient stripes as a background texture",
|
|
219
|
+
message: "Repeating diagonal stripes are a generated-CSS-art cliché (V6).",
|
|
220
|
+
gated: (ctx) => ctx.has("pattern background", "pattern-background", "stripe texture"),
|
|
221
|
+
detect: ({ lines }) =>
|
|
222
|
+
lines.flatMap((l, i) =>
|
|
223
|
+
/background[^;]*repeating-linear-gradient\s*\(/i.test(l)
|
|
224
|
+
? [{ line: i + 1, snippet: l.trim().slice(0, 90) }]
|
|
225
|
+
: [],
|
|
226
|
+
),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "slop-c2-gradient-text",
|
|
230
|
+
catalogId: "C2",
|
|
231
|
+
severity: "P2",
|
|
232
|
+
engines: ["css"],
|
|
233
|
+
description: "Gradient text via background-clip: text",
|
|
234
|
+
message:
|
|
235
|
+
"Gradient text via background-clip:text is overused and has cross-platform rendering edges (C2).",
|
|
236
|
+
gated: (ctx) => ctx.has("gradient identity", "gradient-identity", "gradient logo"),
|
|
237
|
+
detect: ({ content }) =>
|
|
238
|
+
cssBlocks(content)
|
|
239
|
+
.filter(
|
|
240
|
+
(b) =>
|
|
241
|
+
/background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/i.test(b.body) &&
|
|
242
|
+
/linear-gradient|radial-gradient|conic-gradient/i.test(b.body),
|
|
243
|
+
)
|
|
244
|
+
.map((b) => ({ line: b.line, snippet: b.selector.slice(0, 80) })),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "slop-c5-cream-palette",
|
|
248
|
+
catalogId: "C5",
|
|
249
|
+
severity: "P3",
|
|
250
|
+
engines: ["css"],
|
|
251
|
+
description: "Cream/sand background + brass/clay/oxblood accent — the 2025 'premium-consumer' AI palette",
|
|
252
|
+
message:
|
|
253
|
+
"Warm cream/sand + brass/clay is the default 'tasteful AI' surface. Confirm it is first-party in DESIGN.md (C5).",
|
|
254
|
+
gated: (ctx) => ctx.has("warm neutral", "warm-neutral", "cream", "sand", "premium consumer"),
|
|
255
|
+
detect: ({ content }) => {
|
|
256
|
+
const cream = /#f[5-9bdf][f0-9a-f]e[0-9a-f]|#fbf8f1|#f7f5f1|#f5f1ea/i.test(content);
|
|
257
|
+
const brass = /#b08947|#b6553a|#9a2436|brass|oxblood|terracotta|clay/i.test(content);
|
|
258
|
+
if (!cream || !brass) return [];
|
|
259
|
+
const idx = content.search(/#f[5-9bdf][f0-9a-f]e[0-9a-f]|#fbf8f1|#f7f5f1|#f5f1ea/i);
|
|
260
|
+
return [{ line: lineOf(content, Math.max(0, idx)), snippet: "cream/sand bg + warm accent co-occur" }];
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: "slop-t4-eyebrow-overuse",
|
|
265
|
+
catalogId: "T4",
|
|
266
|
+
severity: "P2",
|
|
267
|
+
engines: ["html", "jsx", "css"],
|
|
268
|
+
description: "More than one ALL-CAPS eyebrow per 3 sections",
|
|
269
|
+
message:
|
|
270
|
+
"Eyebrow/ALL-CAPS labels above every section collapse emphasis; cap is 1 per 3 sections (T4).",
|
|
271
|
+
detect: ({ content }) => {
|
|
272
|
+
const sections = (content.match(/<section\b|<Section\b/gi) ?? []).length;
|
|
273
|
+
const eyebrows =
|
|
274
|
+
(content.match(/text-transform\s*:\s*uppercase/gi) ?? []).length +
|
|
275
|
+
(content.match(/(?:class|className)=["'][^"']*\buppercase\b/gi) ?? []).length;
|
|
276
|
+
const cap = Math.max(1, Math.ceil(sections / 3));
|
|
277
|
+
if (sections >= 3 && eyebrows > cap) {
|
|
278
|
+
return [
|
|
279
|
+
{
|
|
280
|
+
line: 1,
|
|
281
|
+
snippet: `${eyebrows} uppercase eyebrows across ${sections} sections (cap ${cap})`,
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
return [];
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "slop-t6-crushed-tracking",
|
|
290
|
+
catalogId: "T6",
|
|
291
|
+
severity: "P2",
|
|
292
|
+
engines: ["css"],
|
|
293
|
+
description: "Crushed display letter-spacing below −0.04em",
|
|
294
|
+
message: "letter-spacing tighter than −0.04em makes glyphs collide optically (T6/Q7).",
|
|
295
|
+
detect: ({ lines }) =>
|
|
296
|
+
lines.flatMap((l, i) => {
|
|
297
|
+
const m = /letter-spacing\s*:\s*(-?\d*\.?\d+)\s*em/i.exec(l);
|
|
298
|
+
if (m && m[1] && parseFloat(m[1]) < -0.04) {
|
|
299
|
+
return [{ line: i + 1, snippet: l.trim().slice(0, 80) }];
|
|
300
|
+
}
|
|
301
|
+
return [];
|
|
302
|
+
}),
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "slop-t7-default-fonts",
|
|
306
|
+
catalogId: "T7",
|
|
307
|
+
severity: "P2",
|
|
308
|
+
engines: ["css"],
|
|
309
|
+
description: "Default AI font (Inter/Roboto/DM Sans/Geist/Space Grotesk/Instrument Serif) without a brand reason",
|
|
310
|
+
message:
|
|
311
|
+
"This is a default AI-coding-tool font pick. Declare it in DESIGN.md with a reason, or choose deliberately (T7).",
|
|
312
|
+
gated: (ctx) => DEFAULT_FONTS.some((f) => ctx.has(f)),
|
|
313
|
+
detect: ({ lines }) =>
|
|
314
|
+
lines.flatMap((l, i) => {
|
|
315
|
+
if (!/font-family\s*:/i.test(l)) return [];
|
|
316
|
+
const lower = l.toLowerCase();
|
|
317
|
+
const hit = DEFAULT_FONTS.find((f) => lower.includes(f));
|
|
318
|
+
return hit ? [{ line: i + 1, snippet: l.trim().slice(0, 80) }] : [];
|
|
319
|
+
}),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "slop-l4-numbered-markers",
|
|
323
|
+
catalogId: "L4",
|
|
324
|
+
severity: "P3",
|
|
325
|
+
engines: ["html", "jsx"],
|
|
326
|
+
description: "Numbered section markers (01 / 02 / 03) as a decorative order device",
|
|
327
|
+
message:
|
|
328
|
+
"Numbered 01/02/03 section markers are a universal generated-landing-page tell (L4).",
|
|
329
|
+
detect: ({ content, ext }) => {
|
|
330
|
+
const text = visibleText(content, ext);
|
|
331
|
+
const markers = text.match(/(?<![\d/.])0[1-9](?![\d%])/g) ?? [];
|
|
332
|
+
const distinct = new Set(markers.map((s) => s.trim()));
|
|
333
|
+
if (distinct.has("01") && distinct.has("02") && distinct.has("03")) {
|
|
334
|
+
return [{ line: 1, snippet: "decorative 01/02/03 section markers" }];
|
|
335
|
+
}
|
|
336
|
+
return [];
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "slop-l8-zindex-magic",
|
|
341
|
+
catalogId: "L8",
|
|
342
|
+
severity: "P2",
|
|
343
|
+
engines: ["css"],
|
|
344
|
+
description: "Magic z-index values (99 / 999 / 9999) with no semantic scale",
|
|
345
|
+
message: "Magic z-index breaks the first time two elements compete. Use a named scale (L8).",
|
|
346
|
+
detect: ({ lines }) =>
|
|
347
|
+
lines.flatMap((l, i) =>
|
|
348
|
+
/z-index\s*:\s*(?:99|999|9999)\b/i.test(l) ? [{ line: i + 1, snippet: l.trim().slice(0, 80) }] : [],
|
|
349
|
+
),
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "slop-m4-transition-all",
|
|
353
|
+
catalogId: "M4",
|
|
354
|
+
severity: "P2",
|
|
355
|
+
engines: ["css"],
|
|
356
|
+
description: "transition: all shorthand",
|
|
357
|
+
message: "transition:all animates layout/color/opacity together — unpredictable + expensive (M4).",
|
|
358
|
+
detect: ({ lines }) =>
|
|
359
|
+
lines.flatMap((l, i) =>
|
|
360
|
+
/transition\s*:\s*all\b/i.test(l) ? [{ line: i + 1, snippet: l.trim().slice(0, 80) }] : [],
|
|
361
|
+
),
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "slop-m2-animate-layout",
|
|
365
|
+
catalogId: "M2",
|
|
366
|
+
severity: "P2",
|
|
367
|
+
engines: ["css"],
|
|
368
|
+
description: "Transitioning layout properties (width/height/top/left/margin/padding)",
|
|
369
|
+
message: "Animating layout props forces reflow; use transform instead (M2).",
|
|
370
|
+
detect: ({ lines }) =>
|
|
371
|
+
lines.flatMap((l, i) =>
|
|
372
|
+
/transition\s*:\s*(?:[^;]*\b)?(?:width|height|top|left|right|bottom|margin|padding)\b[^;]*\d/i.test(l)
|
|
373
|
+
? [{ line: i + 1, snippet: l.trim().slice(0, 80) }]
|
|
374
|
+
: [],
|
|
375
|
+
),
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "slop-cp1-em-dash",
|
|
379
|
+
catalogId: "CP1",
|
|
380
|
+
severity: "P2",
|
|
381
|
+
engines: ["copy"],
|
|
382
|
+
description: "Em-dash overuse in visible copy (> 2 per 500 words)",
|
|
383
|
+
message: "Em-dash density above ~2 per 500 words is the #1 LLM syntactic tell (CP1).",
|
|
384
|
+
detect: ({ content, ext }) => {
|
|
385
|
+
const text = visibleText(content, ext);
|
|
386
|
+
const words = (text.match(/\S+/g) ?? []).length;
|
|
387
|
+
const dashes = (text.match(/—/g) ?? []).length;
|
|
388
|
+
if (words >= 80 && dashes > Math.max(2, Math.round((words / 500) * 2))) {
|
|
389
|
+
return [{ line: 1, snippet: `${dashes} em-dashes in ~${words} words` }];
|
|
390
|
+
}
|
|
391
|
+
return [];
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
id: "slop-cp2-buzzword",
|
|
396
|
+
catalogId: "CP2",
|
|
397
|
+
severity: "P2",
|
|
398
|
+
engines: ["copy"],
|
|
399
|
+
description: "Marketing buzzwords (streamline/empower/supercharge/world-class/…)",
|
|
400
|
+
message: "These words carry no information — delete-test each one (CP2).",
|
|
401
|
+
detect: ({ content, ext }) => {
|
|
402
|
+
const text = visibleText(content, ext);
|
|
403
|
+
const lower = text.toLowerCase();
|
|
404
|
+
const hits = BUZZWORDS.filter((w) => new RegExp(`\\b${w}\\b`, "i").test(lower));
|
|
405
|
+
if (hits.length > 0) {
|
|
406
|
+
return [{ line: 1, snippet: `buzzwords: ${hits.join(", ")}` }];
|
|
407
|
+
}
|
|
408
|
+
return [];
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "slop-lock-shape",
|
|
413
|
+
catalogId: "V8",
|
|
414
|
+
severity: "P2",
|
|
415
|
+
engines: ["css"],
|
|
416
|
+
description: "Shape Lock — too many distinct corner-radius scales",
|
|
417
|
+
message: "Mixed corner-radius scales fragment the shape system; lock one scale (V8 / Shape Lock).",
|
|
418
|
+
gated: (ctx) => ctx.has("shape lock"),
|
|
419
|
+
detect: ({ content }) => {
|
|
420
|
+
const vals = new Set<number>();
|
|
421
|
+
for (const m of content.matchAll(/border-radius\s*:\s*([^;}]+)/gi)) {
|
|
422
|
+
for (const pm of (m[1] ?? "").matchAll(/(\d+(?:\.\d+)?)px/gi)) {
|
|
423
|
+
const v = parseFloat(pm[1] ?? "0");
|
|
424
|
+
if (v > 0 && v < 9990) vals.add(v);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (vals.size >= 4) {
|
|
428
|
+
const idx = content.search(/border-radius/i);
|
|
429
|
+
return [
|
|
430
|
+
{
|
|
431
|
+
line: lineOf(content, Math.max(0, idx)),
|
|
432
|
+
snippet: `${vals.size} distinct radius values: ${[...vals].sort((a, b) => a - b).join("/")}px`,
|
|
433
|
+
},
|
|
434
|
+
];
|
|
435
|
+
}
|
|
436
|
+
return [];
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: "slop-lock-colour",
|
|
441
|
+
catalogId: "C6",
|
|
442
|
+
severity: "P3",
|
|
443
|
+
engines: ["css"],
|
|
444
|
+
description: "Colour Lock — multiple saturated accent hue families",
|
|
445
|
+
message: "Several accent hue families read as no single accent identity; lock one (C6 / Colour Lock).",
|
|
446
|
+
gated: (ctx) => ctx.has("colour lock", "color lock"),
|
|
447
|
+
detect: ({ content }) => {
|
|
448
|
+
const buckets = new Set<number>();
|
|
449
|
+
for (const pm of content.matchAll(/#([0-9a-f]{3}|[0-9a-f]{6})\b/gi)) {
|
|
450
|
+
const b = hueBucket("#" + (pm[1] ?? ""));
|
|
451
|
+
if (b !== null) buckets.add(b);
|
|
452
|
+
}
|
|
453
|
+
if (buckets.size >= 3) {
|
|
454
|
+
const idx = content.search(/#[0-9a-f]{3,6}\b/i);
|
|
455
|
+
return [{ line: lineOf(content, Math.max(0, idx)), snippet: `${buckets.size} distinct accent hue families` }];
|
|
456
|
+
}
|
|
457
|
+
return [];
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: "slop-l9-section-monotony",
|
|
462
|
+
catalogId: "L9",
|
|
463
|
+
severity: "P3",
|
|
464
|
+
engines: ["html", "jsx"],
|
|
465
|
+
description: "Section-layout monotony — too few distinct layout families across many sections",
|
|
466
|
+
message: "Many sections but few distinct layouts → monotonous rhythm; vary the composition (L9).",
|
|
467
|
+
gated: (ctx) => ctx.has("brutalist", "uniform grid", "repetitive grid"),
|
|
468
|
+
detect: ({ content }) => {
|
|
469
|
+
const bodies = sectionBodies(content);
|
|
470
|
+
if (bodies.length < 8) return [];
|
|
471
|
+
const sigs = new Set(bodies.map((b) => sectionSignature(b).sig));
|
|
472
|
+
if (sigs.size < 4) {
|
|
473
|
+
return [{ line: 1, snippet: `${bodies.length} sections, only ${sigs.size} distinct layout families` }];
|
|
474
|
+
}
|
|
475
|
+
return [];
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "slop-l10-zigzag",
|
|
480
|
+
catalogId: "L10",
|
|
481
|
+
severity: "P3",
|
|
482
|
+
engines: ["html", "jsx"],
|
|
483
|
+
description: "Zigzag alternation — more than two consecutive media+text two-column sections",
|
|
484
|
+
message: "More than two consecutive image+text two-column sections is the alternating-zigzag tell (L10).",
|
|
485
|
+
gated: (ctx) => ctx.has("brutalist", "uniform grid", "repetitive grid"),
|
|
486
|
+
detect: ({ content }) => {
|
|
487
|
+
const bodies = sectionBodies(content);
|
|
488
|
+
let run = 0;
|
|
489
|
+
let maxRun = 0;
|
|
490
|
+
for (const b of bodies) {
|
|
491
|
+
if (sectionSignature(b).mediaText) {
|
|
492
|
+
run += 1;
|
|
493
|
+
maxRun = Math.max(maxRun, run);
|
|
494
|
+
} else {
|
|
495
|
+
run = 0;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (maxRun > 2) return [{ line: 1, snippet: `${maxRun} consecutive media+text sections` }];
|
|
499
|
+
return [];
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
];
|
|
@@ -48,7 +48,13 @@
|
|
|
48
48
|
"agency-agents",
|
|
49
49
|
"msitarzewski",
|
|
50
50
|
"610ClaudeSubagents",
|
|
51
|
-
"ChrisRoyse"
|
|
51
|
+
"ChrisRoyse",
|
|
52
|
+
"pbakaus",
|
|
53
|
+
"leonxlnx",
|
|
54
|
+
"gztchan",
|
|
55
|
+
"amaancoderx",
|
|
56
|
+
"npxskillui",
|
|
57
|
+
"\\bskillui\\b"
|
|
52
58
|
],
|
|
53
59
|
"skip_paths": [
|
|
54
60
|
"src/scripts/check_no_external_sources.py",
|
|
@@ -64,6 +64,29 @@ concerns:
|
|
|
64
64
|
script: src/scripts/hooks/block_no_verify.ts
|
|
65
65
|
args: []
|
|
66
66
|
fail_closed: true
|
|
67
|
+
# token-saving Phase 3 — PreToolUse RTK-wrap nudge. DETERMINISTIC: keys off a
|
|
68
|
+
# live `which rtk` probe (not the self-reported flag). When rtk is installed
|
|
69
|
+
# and the agent is about to run a single verbose CLI command that is NOT
|
|
70
|
+
# completeness-critical, warns "re-run wrapped with rtk" (60–90% fewer output
|
|
71
|
+
# tokens). Warn only — never blocks (the v1 contract has no transparent
|
|
72
|
+
# rewrite); never fires when rtk is absent (silent plain-command fallback).
|
|
73
|
+
# Default-OFF (hooks.rtk_wrap.enabled). fail_closed: false — never break a
|
|
74
|
+
# command for a token optimization.
|
|
75
|
+
rtk-wrap:
|
|
76
|
+
script: src/scripts/hooks/rtk_wrap_hook.ts
|
|
77
|
+
args: []
|
|
78
|
+
fail_closed: false
|
|
79
|
+
# road-to-anti-slop-detector Phase 3 — OPTIONAL PreToolUse anti-slop nudge.
|
|
80
|
+
# Runs the lint_design_slop registry against about-to-be-written UI content
|
|
81
|
+
# and WARNS (exit 2) on P0/P1 aesthetic tells. Never blocks (flags, not
|
|
82
|
+
# blocks). Default-OFF (hooks.design_slop.enabled). Anti-loop degradation: a
|
|
83
|
+
# file::rule signature surfaced >=3x goes silent so it never traps the agent.
|
|
84
|
+
# The universal source of truth is the lint_design_slop linter/CI; this hook
|
|
85
|
+
# is a host-limited convenience layer. fail_closed: false.
|
|
86
|
+
design-slop:
|
|
87
|
+
script: src/scripts/hooks/design_slop_hook.ts
|
|
88
|
+
args: []
|
|
89
|
+
fail_closed: false
|
|
67
90
|
# Phase 2 of road-to-hooks-actually-fire-in-consumers — session_start
|
|
68
91
|
# gate that surfaces the marketplace-install-but-unscaffolded shape.
|
|
69
92
|
# Writes .augment/.first-run-action-needed.md + one stderr line so
|
|
@@ -96,7 +119,7 @@ platforms:
|
|
|
96
119
|
session_start: [chat-history, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
97
120
|
session_end: [chat-history]
|
|
98
121
|
stop: [chat-history, verify-before-complete]
|
|
99
|
-
pre_tool_use: [block-no-verify]
|
|
122
|
+
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
100
123
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
101
124
|
|
|
102
125
|
claude:
|
|
@@ -104,7 +127,7 @@ platforms:
|
|
|
104
127
|
session_end: [chat-history]
|
|
105
128
|
stop: [chat-history, verify-before-complete]
|
|
106
129
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
107
|
-
pre_tool_use: [block-no-verify]
|
|
130
|
+
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
108
131
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
109
132
|
|
|
110
133
|
# Cowork — the Claude desktop app's local-agent-mode runtime, built
|
|
@@ -126,7 +149,7 @@ platforms:
|
|
|
126
149
|
session_end: [chat-history]
|
|
127
150
|
stop: [chat-history, verify-before-complete]
|
|
128
151
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
129
|
-
pre_tool_use: [block-no-verify]
|
|
152
|
+
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
130
153
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
131
154
|
|
|
132
155
|
# Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
|