chill-agent 0.0.0-stage β 1.1.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/LICENSE +21 -0
- package/README.md +99 -2
- package/bin/chill-agent.js +7 -0
- package/package.json +38 -4
- package/skills/chill-slop/SKILL.md +96 -0
- package/skills/chill-slop-code/SKILL.md +45 -0
- package/skills/chill-slop-copywriting/SKILL.md +32 -0
- package/skills/chill-slop-human/SKILL.md +24 -0
- package/skills/chill-slop-mobile/SKILL.md +28 -0
- package/skills/chill-slop-ui/SKILL.md +41 -0
- package/src/auditor.js +229 -0
- package/src/cli.js +250 -0
- package/src/detector.js +102 -0
- package/src/index.js +6 -0
- package/src/installer.js +84 -0
- package/src/presets.js +126 -0
- package/src/prompts.js +192 -0
package/src/auditor.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// src/auditor.js - Automated Anti-Slop & Over-Engineering Scanner
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { c } from './prompts.js';
|
|
5
|
+
|
|
6
|
+
const SLOP_PATTERNS = [
|
|
7
|
+
// 1. Visual Slop (Anti-Slop UI)
|
|
8
|
+
{
|
|
9
|
+
id: 'VISUAL-01',
|
|
10
|
+
category: 'Visual Slop',
|
|
11
|
+
severity: 'HIGH',
|
|
12
|
+
regex: /from-purple-[0-9]{3}\s+to-indigo-[0-9]{3}|from-violet-[0-9]{3}\s+to-cyan-[0-9]{3}|from-indigo-[0-9]{3}\s+to-pink-[0-9]{3}/i,
|
|
13
|
+
name: 'Generic AI Purple/Cyan Gradient',
|
|
14
|
+
fix: 'Replace with precision monochromatic carbon `#09090B` or a branded domain color palette.'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'VISUAL-02',
|
|
18
|
+
category: 'Visual Slop',
|
|
19
|
+
severity: 'MEDIUM',
|
|
20
|
+
regex: /rounded-3xl|rounded-full\s+.*shadow-2xl/i,
|
|
21
|
+
name: 'Pill-Shaped Floating Bubble',
|
|
22
|
+
fix: 'Use crisp geometric radius (4px to 8px) with hairline 1px borders instead of rounded-3xl with heavy shadow.'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'VISUAL-03',
|
|
26
|
+
category: 'Visual Slop',
|
|
27
|
+
severity: 'LOW',
|
|
28
|
+
regex: /backdrop-blur-(md|lg|xl|2xl)/i,
|
|
29
|
+
name: 'Excessive Glassmorphic Blur',
|
|
30
|
+
fix: 'Ensure backdrop-blur is not stacked across cards, modals, and navbars simultaneously.'
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// 2. Over-Engineering & Bloat (Ponytail Minimalism)
|
|
34
|
+
{
|
|
35
|
+
id: 'BLOAT-01',
|
|
36
|
+
category: 'Over-Engineering',
|
|
37
|
+
severity: 'HIGH',
|
|
38
|
+
regex: /class\s+[A-Za-z0-9]+Factory|class\s+[A-Za-z0-9]+AbstractBuilder|class\s+[A-Za-z0-9]+ProviderManager/i,
|
|
39
|
+
name: 'Premature Factory / Builder Abstraction',
|
|
40
|
+
fix: 'Ponytail YAGNI rule: replace speculative multi-class factory wrappers with a direct 1-line function or object literal.'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'BLOAT-02',
|
|
44
|
+
category: 'Over-Engineering',
|
|
45
|
+
severity: 'MEDIUM',
|
|
46
|
+
regex: /import\s+.*lodash\/(cloneDeep|isEqual|merge)|import\s+is-number\s+from/i,
|
|
47
|
+
name: 'Unnecessary Micro-Dependency',
|
|
48
|
+
fix: 'Use native JavaScript standard library (`structuredClone`, `Object.assign`, or standard operators) instead of external micro-packages.'
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// 3. Copywriting Slop
|
|
52
|
+
{
|
|
53
|
+
id: 'COPY-01',
|
|
54
|
+
category: 'Copywriting Slop',
|
|
55
|
+
severity: 'HIGH',
|
|
56
|
+
regex: /\b(unleash\s+the\s+power\s+of|supercharge\s+your|seamlessly\s+integrate|next-generation|game-changer|delve\s+into|dive\s+deep\s+into)\b/i,
|
|
57
|
+
name: 'Generic AI Hype Filler',
|
|
58
|
+
fix: 'Replace vague hype words with concrete specifications or direct functional instructions.'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'COPY-02',
|
|
62
|
+
category: 'Copywriting Slop',
|
|
63
|
+
severity: 'MEDIUM',
|
|
64
|
+
regex: /[β¨πππ‘π₯]/u,
|
|
65
|
+
name: 'AI Sparkle / Rocket Emoji ClichΓ©',
|
|
66
|
+
fix: 'Strip emojis from marketing headlines, documentation, and interface button labels.'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'COPY-03',
|
|
70
|
+
category: 'Copywriting Slop',
|
|
71
|
+
severity: 'HIGH',
|
|
72
|
+
regex: /\b(trusted\s+by\s+[0-9,]+\s+happy\s+customers|99\.9%\s+customer\s+satisfaction)\b/i,
|
|
73
|
+
name: 'Hallucinated Social Proof',
|
|
74
|
+
fix: 'Cite verified customer metrics or remove synthetic testimonials entirely.'
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// 4. Code Hygiene Slop
|
|
78
|
+
{
|
|
79
|
+
id: 'CODE-01',
|
|
80
|
+
category: 'Code Hygiene',
|
|
81
|
+
severity: 'MEDIUM',
|
|
82
|
+
regex: /\/\/\s*(this\s+function\s+calculates|this\s+is\s+the\s+render\s+method|define\s+variables?|initialize\s+state)/i,
|
|
83
|
+
name: 'Redundant Obvious AI Code Comment',
|
|
84
|
+
fix: 'Remove comments that merely restate what the code visibly does. Document the "WHY", not the "WHAT".'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'CODE-02',
|
|
88
|
+
category: 'Code Hygiene',
|
|
89
|
+
severity: 'LOW',
|
|
90
|
+
regex: /\/\/\s*TODO:\s*(implement\s+this|add\s+logic\s+later|finish\s+this)/i,
|
|
91
|
+
name: 'Unimplemented Lazy AI Stub',
|
|
92
|
+
fix: 'Provide full working implementation or throw explicit NotImplementedError with actionable message.'
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// 5. Human Factors & Accessibility
|
|
96
|
+
{
|
|
97
|
+
id: 'A11Y-01',
|
|
98
|
+
category: 'Human Factors',
|
|
99
|
+
severity: 'HIGH',
|
|
100
|
+
regex: /<button[^>]*>[^<]*<svg[^>]*>[\s\S]*?<\/svg>[^<]*<\/button>/i,
|
|
101
|
+
name: 'Icon Button Missing Accessible Label',
|
|
102
|
+
fix: 'Add aria-label="..." to the button element so screen readers can identify the control action.'
|
|
103
|
+
}
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const SCAN_EXTENSIONS = new Set([
|
|
107
|
+
'.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.html', '.css', '.md', '.mdx'
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const IGNORE_DIRS = new Set([
|
|
111
|
+
'node_modules', '.git', '.chillrewind', 'dist', 'build', '.next', '.cache', 'coverage'
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
export function scanFile(filePath) {
|
|
115
|
+
const findings = [];
|
|
116
|
+
try {
|
|
117
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
118
|
+
const lines = content.split('\n');
|
|
119
|
+
|
|
120
|
+
lines.forEach((line, lineIdx) => {
|
|
121
|
+
for (const pattern of SLOP_PATTERNS) {
|
|
122
|
+
if (pattern.regex.test(line)) {
|
|
123
|
+
findings.push({
|
|
124
|
+
file: filePath,
|
|
125
|
+
line: lineIdx + 1,
|
|
126
|
+
snippet: line.trim().slice(0, 80),
|
|
127
|
+
...pattern
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
} catch {}
|
|
133
|
+
return findings;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function autoFixFile(filePath) {
|
|
137
|
+
let modified = false;
|
|
138
|
+
try {
|
|
139
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
140
|
+
|
|
141
|
+
// 1. Strip marketing emojis
|
|
142
|
+
const strippedEmoji = content.replace(/[β¨πππ‘π₯]/gu, '');
|
|
143
|
+
if (strippedEmoji !== content) {
|
|
144
|
+
content = strippedEmoji;
|
|
145
|
+
modified = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2. Fix redundant obvious comments
|
|
149
|
+
const strippedComments = content.replace(/\/\/\s*(this\s+function\s+calculates|this\s+is\s+the\s+render\s+method|define\s+variables?|initialize\s+state).*\n/gi, '');
|
|
150
|
+
if (strippedComments !== content) {
|
|
151
|
+
content = strippedComments;
|
|
152
|
+
modified = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. Fix rounded-3xl cards
|
|
156
|
+
const fixedRadius = content.replace(/\brounded-3xl\b/g, 'rounded-lg');
|
|
157
|
+
if (fixedRadius !== content) {
|
|
158
|
+
content = fixedRadius;
|
|
159
|
+
modified = true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (modified) {
|
|
163
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
164
|
+
}
|
|
165
|
+
} catch {}
|
|
166
|
+
return modified;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function scanDirectory(dirPath) {
|
|
170
|
+
const allFindings = [];
|
|
171
|
+
|
|
172
|
+
function walk(current) {
|
|
173
|
+
let entries = [];
|
|
174
|
+
try {
|
|
175
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
183
|
+
walk(path.join(current, entry.name));
|
|
184
|
+
}
|
|
185
|
+
} else if (entry.isFile()) {
|
|
186
|
+
const ext = path.extname(entry.name);
|
|
187
|
+
if (SCAN_EXTENSIONS.has(ext)) {
|
|
188
|
+
const res = scanFile(path.join(current, entry.name));
|
|
189
|
+
allFindings.push(...res);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
walk(dirPath);
|
|
196
|
+
return allFindings;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function printAuditReport(findings, targetDir = process.cwd()) {
|
|
200
|
+
console.log(`\n${c.bold}ChillAgent Anti-Slop & Over-Engineering Audit${c.reset}`);
|
|
201
|
+
console.log(`${c.dim}Scanned directory: ${targetDir}${c.reset}\n`);
|
|
202
|
+
|
|
203
|
+
if (findings.length === 0) {
|
|
204
|
+
console.log(`${c.green}${c.bold}β Clean Codebase!${c.reset} No AI slop patterns or premature over-engineering found.`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const highCount = findings.filter((f) => f.severity === 'HIGH').length;
|
|
209
|
+
const medCount = findings.filter((f) => f.severity === 'MEDIUM').length;
|
|
210
|
+
const lowCount = findings.filter((f) => f.severity === 'LOW').length;
|
|
211
|
+
|
|
212
|
+
console.log(
|
|
213
|
+
`Found ${c.bold}${findings.length} infractions${c.reset} ` +
|
|
214
|
+
`(${c.coral}${highCount} High${c.reset}, ${c.amber}${medCount} Medium${c.reset}, ${c.gray}${lowCount} Low${c.reset}):\n`
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
findings.forEach((f, idx) => {
|
|
218
|
+
const sevColor = f.severity === 'HIGH' ? c.coral : f.severity === 'MEDIUM' ? c.amber : c.gray;
|
|
219
|
+
const relFile = path.relative(targetDir, f.file);
|
|
220
|
+
|
|
221
|
+
console.log(
|
|
222
|
+
`${c.bold}#${idx + 1}${c.reset} [${sevColor}${f.severity}${c.reset}] ` +
|
|
223
|
+
`${c.cyan}${f.category}${c.reset} β ${c.bold}${f.name}${c.reset}`
|
|
224
|
+
);
|
|
225
|
+
console.log(` ${c.dim}Location:${c.reset} ${relFile}:${c.line}`);
|
|
226
|
+
console.log(` ${c.dim}Snippet:${c.reset} ${c.italic}"${f.snippet}"${c.reset}`);
|
|
227
|
+
console.log(` ${c.emerald}Fix:${c.reset} ${f.fix}\n`);
|
|
228
|
+
});
|
|
229
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/cli.js - ChillAgent CLI Command Router
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { banner, c, select, multiselect, confirm } from './prompts.js';
|
|
6
|
+
import { detectAgents } from './detector.js';
|
|
7
|
+
import { AVAILABLE_SKILLS, copySkillsToProject, generatePointerBlock, injectPointerIntoFile } from './installer.js';
|
|
8
|
+
import { scanDirectory, printAuditReport } from './auditor.js';
|
|
9
|
+
import { DESIGN_PRESETS } from './presets.js';
|
|
10
|
+
|
|
11
|
+
export async function runCli() {
|
|
12
|
+
const rawArgs = process.argv.slice(2);
|
|
13
|
+
const command = rawArgs[0] || 'wizard';
|
|
14
|
+
|
|
15
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
16
|
+
banner();
|
|
17
|
+
console.log(`
|
|
18
|
+
${c.bold}USAGE:${c.reset}
|
|
19
|
+
npx chill-agent Launch interactive setup wizard
|
|
20
|
+
npx chill-agent install [--all] [--agent <name>] Install anti-slop skills non-interactively
|
|
21
|
+
npx chill-agent audit [dir] [--json] Scan code & copy for AI slop cliches
|
|
22
|
+
npx chill-agent init-design [preset] Generate a high-craft DESIGN.md specification
|
|
23
|
+
npx chill-agent doctor Inspect detected coding agents and rule health
|
|
24
|
+
npx chill-agent rewind [subcommand] Run deterministic time-travel debugger & proxy
|
|
25
|
+
|
|
26
|
+
${c.bold}PRESETS FOR init-design:${c.reset}
|
|
27
|
+
linear-carbon Precision carbon monochrome, 1px hairlines (Default)
|
|
28
|
+
swiss-brutal Stark black/white contrast, zero border radius
|
|
29
|
+
technical-spec Industrial data-first layout, telemetry density
|
|
30
|
+
editorial-paper Warm ivory paper, literary serif headings
|
|
31
|
+
micro-density Cyberpunk HUD, compact phosphor-green telemetry
|
|
32
|
+
`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (command === 'doctor') {
|
|
37
|
+
banner();
|
|
38
|
+
console.log(`${c.bold}ChillAgent Environment Diagnostics${c.reset}\n`);
|
|
39
|
+
const agents = detectAgents();
|
|
40
|
+
agents.forEach((ag) => {
|
|
41
|
+
const statusIcon = ag.isDetected ? `${c.emerald}β DETECTED${c.reset}` : `${c.gray}β NOT FOUND${c.reset}`;
|
|
42
|
+
console.log(` ${ag.icon} ${c.bold}${ag.name.padEnd(30)}${c.reset} ${statusIcon} ${c.dim}(${ag.matchType})${c.reset}`);
|
|
43
|
+
});
|
|
44
|
+
console.log();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (command === 'audit' || command === 'lint' || command === 'scan') {
|
|
49
|
+
const isFix = rawArgs.includes('--fix');
|
|
50
|
+
const targetDir = rawArgs[1] && !rawArgs[1].startsWith('--')
|
|
51
|
+
? path.resolve(process.cwd(), rawArgs[1])
|
|
52
|
+
: process.cwd();
|
|
53
|
+
|
|
54
|
+
if (isFix) {
|
|
55
|
+
const { autoFixFile } = await import('./auditor.js');
|
|
56
|
+
let fixCount = 0;
|
|
57
|
+
function walkFix(dir) {
|
|
58
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
59
|
+
for (const e of entries) {
|
|
60
|
+
if (e.isDirectory() && !['node_modules', '.git', '.chillrewind', 'dist'].includes(e.name)) {
|
|
61
|
+
walkFix(path.join(dir, e.name));
|
|
62
|
+
} else if (e.isFile() && ['.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.md'].some(ext => e.name.endsWith(ext))) {
|
|
63
|
+
if (autoFixFile(path.join(dir, e.name))) fixCount++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
walkFix(targetDir);
|
|
68
|
+
console.log(`\n${c.emerald}β Anti-Slop Auto-Fix Completed:${c.reset} Remedied slop patterns in ${c.bold}${fixCount}${c.reset} files.`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const findings = scanDirectory(targetDir);
|
|
72
|
+
if (rawArgs.includes('--json')) {
|
|
73
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
74
|
+
} else {
|
|
75
|
+
printAuditReport(findings, targetDir);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (command === 'init-design') {
|
|
81
|
+
const presetId = rawArgs[1];
|
|
82
|
+
let selectedPreset = DESIGN_PRESETS.find((p) => p.id === presetId);
|
|
83
|
+
|
|
84
|
+
if (!selectedPreset) {
|
|
85
|
+
banner();
|
|
86
|
+
const choice = await select({
|
|
87
|
+
message: 'Select a High-Craft Aesthetic Preset for DESIGN.md:',
|
|
88
|
+
choices: DESIGN_PRESETS.map((p) => ({
|
|
89
|
+
label: p.name,
|
|
90
|
+
value: p.id,
|
|
91
|
+
description: p.description
|
|
92
|
+
}))
|
|
93
|
+
});
|
|
94
|
+
selectedPreset = DESIGN_PRESETS.find((p) => p.id === choice);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (selectedPreset) {
|
|
98
|
+
const destPath = path.join(process.cwd(), 'DESIGN.md');
|
|
99
|
+
fs.writeFileSync(destPath, selectedPreset.content, 'utf8');
|
|
100
|
+
console.log(`\n${c.emerald}β Generated DESIGN.md${c.reset} using '${c.bold}${selectedPreset.name}${c.reset}' preset.`);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (command === 'install') {
|
|
106
|
+
const { values } = parseArgs({
|
|
107
|
+
args: rawArgs.slice(1),
|
|
108
|
+
options: {
|
|
109
|
+
all: { type: 'boolean', default: false },
|
|
110
|
+
agent: { type: 'string', short: 'a' }
|
|
111
|
+
},
|
|
112
|
+
strict: false
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const skillsToInstall = values.all
|
|
116
|
+
? AVAILABLE_SKILLS.map((s) => s.id)
|
|
117
|
+
: ['chill-slop', 'chill-slop-ui', 'chill-slop-copywriting', 'chill-slop-code'];
|
|
118
|
+
|
|
119
|
+
copySkillsToProject(skillsToInstall);
|
|
120
|
+
|
|
121
|
+
const agents = detectAgents();
|
|
122
|
+
let targetAgents = agents.filter((ag) => ag.isDetected);
|
|
123
|
+
if (values.agent) {
|
|
124
|
+
targetAgents = agents.filter((ag) => ag.id === values.agent);
|
|
125
|
+
}
|
|
126
|
+
if (targetAgents.length === 0) {
|
|
127
|
+
targetAgents = [agents.find((ag) => ag.id === 'antigravity') || agents[0]];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const pointer = generatePointerBlock(skillsToInstall);
|
|
131
|
+
for (const ag of targetAgents) {
|
|
132
|
+
const targetFilePath = path.join(process.cwd(), ag.targetFile);
|
|
133
|
+
injectPointerIntoFile(targetFilePath, pointer);
|
|
134
|
+
console.log(`${c.emerald}β Injected Anti-Slop into ${ag.name}:${c.reset} ${ag.targetFile}`);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (command === 'rewind') {
|
|
140
|
+
const rewindCliPath = path.resolve(__dirname, '..', 'chillrewind', 'src', 'cli.ts');
|
|
141
|
+
if (!fs.existsSync(rewindCliPath)) {
|
|
142
|
+
console.error(`${c.coral}ChillRewind engine not found at ${rewindCliPath}${c.reset}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const { spawn } = await import('node:child_process');
|
|
146
|
+
spawn('node', ['--experimental-strip-types', '--no-warnings', rewindCliPath, ...rawArgs.slice(1)], {
|
|
147
|
+
stdio: 'inherit'
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Interactive Wizard (Default)
|
|
153
|
+
banner();
|
|
154
|
+
console.log(`${c.dim}Setting up ChillAgent Anti-Slop 2.0 in ${process.cwd()}${c.reset}\n`);
|
|
155
|
+
|
|
156
|
+
// Step 1: Detect Agents
|
|
157
|
+
const agents = detectAgents();
|
|
158
|
+
const detectedList = agents.filter((a) => a.isDetected);
|
|
159
|
+
|
|
160
|
+
let selectedAgents = [];
|
|
161
|
+
if (detectedList.length > 0) {
|
|
162
|
+
console.log(`${c.bold}Detected Agent Environments in Workspace:${c.reset}`);
|
|
163
|
+
detectedList.forEach((ag) => console.log(` ${ag.icon} ${c.cyan}${ag.name}${c.reset} ${c.dim}(${ag.matchType})${c.reset}`));
|
|
164
|
+
console.log();
|
|
165
|
+
|
|
166
|
+
selectedAgents = await multiselect({
|
|
167
|
+
message: 'Select target coding agents to configure:',
|
|
168
|
+
choices: agents.map((ag) => ({
|
|
169
|
+
label: `${ag.icon} ${ag.name}`,
|
|
170
|
+
value: ag.id,
|
|
171
|
+
description: ag.isDetected ? 'Detected' : 'Standard'
|
|
172
|
+
})),
|
|
173
|
+
defaultSelected: detectedList.map((ag) => ag.id)
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
selectedAgents = await multiselect({
|
|
177
|
+
message: 'Select target coding agents to configure:',
|
|
178
|
+
choices: agents.map((ag) => ({
|
|
179
|
+
label: `${ag.icon} ${ag.name}`,
|
|
180
|
+
value: ag.id
|
|
181
|
+
})),
|
|
182
|
+
defaultSelected: ['antigravity', 'claude']
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (selectedAgents.length === 0) {
|
|
187
|
+
console.log(`${c.yellow}No agents selected. Exiting.${c.reset}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Step 2: Choose Skills
|
|
192
|
+
console.log();
|
|
193
|
+
const selectedSkills = await multiselect({
|
|
194
|
+
message: 'Select Anti-Slop skills to install into this project:',
|
|
195
|
+
choices: AVAILABLE_SKILLS.map((s) => ({
|
|
196
|
+
label: s.label,
|
|
197
|
+
value: s.id,
|
|
198
|
+
description: s.description
|
|
199
|
+
})),
|
|
200
|
+
defaultSelected: AVAILABLE_SKILLS.map((s) => s.id) // All by default
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Step 3: Design Direction
|
|
204
|
+
console.log();
|
|
205
|
+
const needDesignMd = !fs.existsSync(path.join(process.cwd(), 'DESIGN.md'));
|
|
206
|
+
if (needDesignMd) {
|
|
207
|
+
const wantDesign = await confirm({
|
|
208
|
+
message: 'No DESIGN.md found. Initialize a High-Craft aesthetic spec to avoid sterile AI defaults?'
|
|
209
|
+
});
|
|
210
|
+
if (wantDesign) {
|
|
211
|
+
const presetId = await select({
|
|
212
|
+
message: 'Pick an aesthetic foundation for DESIGN.md:',
|
|
213
|
+
choices: DESIGN_PRESETS.map((p) => ({
|
|
214
|
+
label: p.name,
|
|
215
|
+
value: p.id,
|
|
216
|
+
description: p.description
|
|
217
|
+
}))
|
|
218
|
+
});
|
|
219
|
+
const chosen = DESIGN_PRESETS.find((p) => p.id === presetId);
|
|
220
|
+
if (chosen) {
|
|
221
|
+
fs.writeFileSync(path.join(process.cwd(), 'DESIGN.md'), chosen.content, 'utf8');
|
|
222
|
+
console.log(`${c.emerald}β Generated DESIGN.md${c.reset} (${chosen.name})`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Step 4: Perform Installation
|
|
228
|
+
console.log(`\n${c.bold}Installing selected skills & injecting agent hooks...${c.reset}`);
|
|
229
|
+
const copied = copySkillsToProject(selectedSkills);
|
|
230
|
+
console.log(`${c.emerald}β Copied ${copied.length} skills into:${c.reset} ./skills/`);
|
|
231
|
+
|
|
232
|
+
const pointer = generatePointerBlock(selectedSkills);
|
|
233
|
+
for (const agentId of selectedAgents) {
|
|
234
|
+
const ag = agents.find((a) => a.id === agentId);
|
|
235
|
+
if (ag) {
|
|
236
|
+
const targetFilePath = path.join(process.cwd(), ag.targetFile);
|
|
237
|
+
injectPointerIntoFile(targetFilePath, pointer);
|
|
238
|
+
console.log(`${c.emerald}β Configured ${ag.name}:${c.reset} ${ag.targetFile}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log(`
|
|
243
|
+
${c.green}${c.bold}β ChillAgent Anti-Slop 2.0 Successfully Activated!${c.reset}
|
|
244
|
+
${c.dim}------------------------------------------------------------${c.reset}
|
|
245
|
+
${c.bold}Next Steps:${c.reset}
|
|
246
|
+
β’ Run ${c.cyan}npx chill-agent audit${c.reset} to scan existing files for AI slop cliches.
|
|
247
|
+
β’ Run ${c.cyan}npx chill-agent rewind ui${c.reset} to inspect agent time-travel timelines.
|
|
248
|
+
β’ Start coding in your agent; all anti-slop rules are now strictly enforced!
|
|
249
|
+
`);
|
|
250
|
+
}
|
package/src/detector.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/detector.js - Coding Agent Environment & Configuration Detector
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
|
|
6
|
+
export function getHomeDir() {
|
|
7
|
+
return os.homedir();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function detectAgents(projectRoot = process.cwd()) {
|
|
11
|
+
const home = getHomeDir();
|
|
12
|
+
const detected = [];
|
|
13
|
+
|
|
14
|
+
const candidates = [
|
|
15
|
+
{
|
|
16
|
+
id: 'antigravity',
|
|
17
|
+
name: 'Antigravity (Google / AGY)',
|
|
18
|
+
icon: 'π',
|
|
19
|
+
projectFiles: ['GEMINI.md', 'AGENTS.md'],
|
|
20
|
+
globalPaths: [
|
|
21
|
+
path.join(home, '.gemini', 'antigravity'),
|
|
22
|
+
path.join(home, '.gemini', 'config', 'skills')
|
|
23
|
+
],
|
|
24
|
+
targetFile: 'GEMINI.md'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'claude',
|
|
28
|
+
name: 'Claude Code (Anthropic)',
|
|
29
|
+
icon: 'π€',
|
|
30
|
+
projectFiles: ['CLAUDE.md', '.claude-plugin'],
|
|
31
|
+
globalPaths: [path.join(home, '.claude')],
|
|
32
|
+
targetFile: 'CLAUDE.md'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'cursor',
|
|
36
|
+
name: 'Cursor IDE',
|
|
37
|
+
icon: 'β‘',
|
|
38
|
+
projectFiles: ['.cursorrules', '.cursor/rules'],
|
|
39
|
+
globalPaths: [path.join(home, '.cursor')],
|
|
40
|
+
targetFile: fs.existsSync(path.join(projectRoot, '.cursor', 'rules'))
|
|
41
|
+
? path.join('.cursor', 'rules', 'chill-agent.mdc')
|
|
42
|
+
: '.cursorrules'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'windsurf',
|
|
46
|
+
name: 'Windsurf (Codeium)',
|
|
47
|
+
icon: 'π',
|
|
48
|
+
projectFiles: ['.windsurfrules', '.windsurf'],
|
|
49
|
+
globalPaths: [],
|
|
50
|
+
targetFile: '.windsurfrules'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'codex',
|
|
54
|
+
name: 'OpenAI Codex / OpenCode',
|
|
55
|
+
icon: 'π§ ',
|
|
56
|
+
projectFiles: ['AGENTS.md', '.codex-plugin'],
|
|
57
|
+
globalPaths: [path.join(home, '.codex')],
|
|
58
|
+
targetFile: 'AGENTS.md'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'copilot',
|
|
62
|
+
name: 'GitHub Copilot Workspace',
|
|
63
|
+
icon: 'π',
|
|
64
|
+
projectFiles: ['.github/copilot-instructions.md'],
|
|
65
|
+
globalPaths: [],
|
|
66
|
+
targetFile: path.join('.github', 'copilot-instructions.md')
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const agent of candidates) {
|
|
71
|
+
let found = false;
|
|
72
|
+
let matchType = 'none';
|
|
73
|
+
|
|
74
|
+
// Check project-level existence
|
|
75
|
+
for (const rel of agent.projectFiles) {
|
|
76
|
+
if (fs.existsSync(path.join(projectRoot, rel))) {
|
|
77
|
+
found = true;
|
|
78
|
+
matchType = `project (${rel})`;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check global installation
|
|
84
|
+
if (!found) {
|
|
85
|
+
for (const gPath of agent.globalPaths) {
|
|
86
|
+
if (fs.existsSync(gPath)) {
|
|
87
|
+
found = true;
|
|
88
|
+
matchType = 'global';
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
detected.push({
|
|
95
|
+
...agent,
|
|
96
|
+
isDetected: found,
|
|
97
|
+
matchType
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return detected;
|
|
102
|
+
}
|
package/src/index.js
ADDED
package/src/installer.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/installer.js - Multi-Agent Skill Injector & Config Manager
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { c } from './prompts.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
9
|
+
|
|
10
|
+
export const AVAILABLE_SKILLS = [
|
|
11
|
+
{ id: 'chill-slop', label: 'ChillSlop Master Filter (Anti-Slop + Ponytail Core)', description: 'Hard gates, YAGNI ladder, craftsmanship standard' },
|
|
12
|
+
{ id: 'chill-slop-code', label: 'ChillSlop Pragmatic Code', description: 'Smallest complete change, native APIs, zero over-engineering' },
|
|
13
|
+
{ id: 'chill-slop-ui', label: 'ChillSlop UI & Layout', description: 'Monochrome surfaces, 1px hairlines, non-generic cards' },
|
|
14
|
+
{ id: 'chill-slop-copywriting', label: 'ChillSlop Copywriting', description: 'Strip AI buzzwords, vague hype, fake testimonials' },
|
|
15
|
+
{ id: 'chill-slop-human', label: 'ChillSlop Ergonomics & A11y', description: 'WCAG 4.5:1 contrast, visible focus rings, 44px tap targets' },
|
|
16
|
+
{ id: 'chill-slop-mobile', label: 'ChillSlop Mobile & Fluid', description: 'Clamp scaling, thumb reach zones, zero overflow' }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function generatePointerBlock(selectedSkills = ['chill-slop'], intensity = 'full') {
|
|
20
|
+
const lines = [
|
|
21
|
+
'<!-- chill-agent:start -->',
|
|
22
|
+
'## ChillAgent Anti-Slop 2.0 & Pragmatic Senior Dev Standard (Ponytail + Anti-Slop)',
|
|
23
|
+
`Active Mode: \`${intensity}\` (Options: lite | full | ultra). Switch anytime with \`/chill <mode>\`.`,
|
|
24
|
+
'',
|
|
25
|
+
'Before generating or editing any code, interface, copy, or architecture in this workspace, follow these active skills:',
|
|
26
|
+
...selectedSkills.map((s) => `- \`skills/${s}/SKILL.md\``),
|
|
27
|
+
'',
|
|
28
|
+
'### Core Operating Rules:',
|
|
29
|
+
'1. **The Smallest Complete Change (Ponytail):** Take the shortest path that solves the whole task: standard library > existing codebase helper > installed dependency > minimum new code. No premature abstractions or wrapper factories.',
|
|
30
|
+
'2. **Purpose Test (Anti-Slop):** Every visual decision, color accent, layout structure, and copy phrase must name a specific domain purpose. AI defaults (purple gradients, rounded-3xl cards, generic cards) are forbidden.',
|
|
31
|
+
'3. **Liveliness Bar:** Avoid sterile defaults by reading `DESIGN.md`. If `DESIGN.md` is missing, adhere strictly to Linear/Geist carbon monochromatic standards.',
|
|
32
|
+
'4. **Context Hygiene & Time-Travel:** Strip conversational fluff and emojis. When debugging stochastic agent loops, route upstream LLM calls through `chillrewind run` (deterministic SQLite WAL replay + SlopSieve circuit breaker).',
|
|
33
|
+
'5. **Delivery Disclosure:** End non-trivial deliveries with a 1-line disclosure: what was skipped and known limits/trade-offs.',
|
|
34
|
+
'<!-- chill-agent:end -->'
|
|
35
|
+
];
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function copySkillsToProject(selectedSkills, targetDir = process.cwd()) {
|
|
40
|
+
const destSkillsDir = path.join(targetDir, 'skills');
|
|
41
|
+
if (!fs.existsSync(destSkillsDir)) fs.mkdirSync(destSkillsDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const copied = [];
|
|
44
|
+
for (const skillId of selectedSkills) {
|
|
45
|
+
const srcDir = path.join(ROOT_DIR, 'skills', skillId);
|
|
46
|
+
const destDir = path.join(destSkillsDir, skillId);
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(srcDir)) {
|
|
49
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
50
|
+
const srcSkillMd = path.join(srcDir, 'SKILL.md');
|
|
51
|
+
const destSkillMd = path.join(destDir, 'SKILL.md');
|
|
52
|
+
if (fs.existsSync(srcSkillMd)) {
|
|
53
|
+
fs.copyFileSync(srcSkillMd, destSkillMd);
|
|
54
|
+
copied.push(skillId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return copied;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function injectPointerIntoFile(targetFilePath, pointerBlock) {
|
|
62
|
+
let existingContent = '';
|
|
63
|
+
if (fs.existsSync(targetFilePath)) {
|
|
64
|
+
existingContent = fs.readFileSync(targetFilePath, 'utf8');
|
|
65
|
+
} else {
|
|
66
|
+
const parentDir = path.dirname(targetFilePath);
|
|
67
|
+
if (!fs.existsSync(parentDir)) fs.mkdirSync(parentDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const startMarker = '<!-- chill-agent:start -->';
|
|
71
|
+
const endMarker = '<!-- chill-agent:end -->';
|
|
72
|
+
|
|
73
|
+
let newContent = '';
|
|
74
|
+
if (existingContent.includes(startMarker) && existingContent.includes(endMarker)) {
|
|
75
|
+
const before = existingContent.substring(0, existingContent.indexOf(startMarker));
|
|
76
|
+
const after = existingContent.substring(existingContent.indexOf(endMarker) + endMarker.length);
|
|
77
|
+
newContent = `${before.trimEnd()}\n\n${pointerBlock}\n${after.trimStart()}`.trim() + '\n';
|
|
78
|
+
} else {
|
|
79
|
+
newContent = existingContent.trim() ? `${existingContent.trim()}\n\n${pointerBlock}\n` : `${pointerBlock}\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(targetFilePath, newContent, 'utf8');
|
|
83
|
+
return true;
|
|
84
|
+
}
|