chill-agent 1.4.0 → 1.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.
Files changed (67) hide show
  1. package/.agents/rules/ponytail.md +30 -0
  2. package/.claude-plugin/marketplace.json +17 -0
  3. package/.clinerules/ponytail.md +30 -0
  4. package/.cursor/rules/ponytail.mdc +36 -0
  5. package/.grok-plugin/marketplace.json +16 -0
  6. package/.kiro/steering/ponytail.md +35 -0
  7. package/.openclaw/skills/ponytail/SKILL.md +52 -0
  8. package/.openclaw/skills/ponytail-audit/SKILL.md +103 -0
  9. package/.openclaw/skills/ponytail-debt/SKILL.md +44 -0
  10. package/.openclaw/skills/ponytail-gain/SKILL.md +52 -0
  11. package/.openclaw/skills/ponytail-help/SKILL.md +76 -0
  12. package/.openclaw/skills/ponytail-review/SKILL.md +101 -0
  13. package/.opencode/command/ponytail-audit.md +5 -0
  14. package/.opencode/command/ponytail-debt.md +5 -0
  15. package/.opencode/command/ponytail-gain.md +5 -0
  16. package/.opencode/command/ponytail-help.md +5 -0
  17. package/.opencode/command/ponytail-review.md +5 -0
  18. package/.opencode/command/ponytail.md +5 -0
  19. package/.opencode/plugins/index.js +4 -0
  20. package/.opencode/plugins/ponytail-frontmatter.cjs +63 -0
  21. package/.opencode/plugins/ponytail.mjs +206 -0
  22. package/.windsurf/rules/ponytail.md +30 -0
  23. package/AGENTS.md +19 -0
  24. package/__init__.py +228 -0
  25. package/commands/ponytail-audit.toml +2 -0
  26. package/commands/ponytail-debt.toml +2 -0
  27. package/commands/ponytail-gain.toml +2 -0
  28. package/commands/ponytail-help.toml +2 -0
  29. package/commands/ponytail-review.toml +2 -0
  30. package/commands/ponytail.toml +2 -0
  31. package/hooks/chill-statusline.ps1 +1 -1
  32. package/hooks/ponytail-activate.js +201 -0
  33. package/hooks/ponytail-instructions.js +98 -0
  34. package/hooks/ponytail-mode-tracker.js +216 -0
  35. package/hooks/ponytail-runtime.js +182 -0
  36. package/hooks/ponytail-statusline.ps1 +24 -0
  37. package/hooks/ponytail-statusline.sh +18 -0
  38. package/package.json +9 -3
  39. package/pi-extension/index.js +254 -0
  40. package/pi-extension/package.json +8 -0
  41. package/scripts/check-rule-copies.js +74 -0
  42. package/scripts/check-versions.js +77 -0
  43. package/scripts/cursor-hooks.js +145 -0
  44. package/scripts/generate-chillslop.mjs +42 -0
  45. package/scripts/generate-examples.mjs +149 -0
  46. package/scripts/generate-plugin-doors.mjs +108 -0
  47. package/scripts/sync-ponytail.mjs +65 -0
  48. package/scripts/uninstall.js +92 -0
  49. package/skills/ponytail/SKILL.md +52 -0
  50. package/skills/ponytail-audit/SKILL.md +103 -0
  51. package/skills/ponytail-debt/SKILL.md +44 -0
  52. package/skills/ponytail-gain/SKILL.md +52 -0
  53. package/skills/ponytail-help/SKILL.md +76 -0
  54. package/skills/ponytail-review/SKILL.md +101 -0
  55. package/tests/commands.test.js +94 -0
  56. package/tests/copilot-plugin.test.js +35 -0
  57. package/tests/correctness.test.js +245 -0
  58. package/tests/cursor-hooks.test.js +343 -0
  59. package/tests/gemini-extension.test.js +91 -0
  60. package/tests/grok-plugin.test.js +39 -0
  61. package/tests/hermes-plugin.test.js +403 -0
  62. package/tests/hooks.test.js +1005 -0
  63. package/tests/kimi-plugin.test.js +25 -0
  64. package/tests/loc.test.js +18 -0
  65. package/tests/openclaw-skills.test.js +26 -0
  66. package/tests/opencode-plugin.test.js +340 -0
  67. package/tests/qoder-plugin.test.js +84 -0
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ // ponytail — install or remove the Cursor hooks (hooks/cursor-hooks.json) in
3
+ // ~/.cursor/hooks.json (default) or <cwd>/.cursor/hooks.json (--project),
4
+ // merging with whatever hooks are already there. Only entries that run one of
5
+ // ponytail's own hooks/ponytail-*.js scripts are added or removed; every other
6
+ // hook stays as it was.
7
+ //
8
+ // node scripts/cursor-hooks.js install [--project]
9
+ // node scripts/cursor-hooks.js uninstall [--project]
10
+
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { isShellSafe } = require('../hooks/ponytail-config');
15
+
16
+ const ROOT = path.join(__dirname, '..');
17
+ const TEMPLATE = path.join(ROOT, 'hooks', 'cursor-hooks.json');
18
+ const PONYTAIL_HOOK = /[\\/]hooks[\\/]ponytail-[\w-]+\.js(?=["'\s]|$)/;
19
+
20
+ function isPonytailHook(entry) {
21
+ return Boolean(entry && typeof entry.command === 'string' && PONYTAIL_HOOK.test(entry.command));
22
+ }
23
+
24
+ function hooksPath(scope) {
25
+ return scope === 'project'
26
+ ? path.join(process.cwd(), '.cursor', 'hooks.json')
27
+ : path.join(os.homedir(), '.cursor', 'hooks.json');
28
+ }
29
+
30
+ // Missing file → empty config. Malformed JSON throws a SyntaxError so the
31
+ // caller can refuse to touch the file rather than overwrite it.
32
+ function readConfig(file) {
33
+ let config = {};
34
+ try {
35
+ config = JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
36
+ } catch (e) {
37
+ if (e.code !== 'ENOENT') throw e;
38
+ }
39
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
40
+ throw new Error(file + ' must contain a JSON object; nothing was changed');
41
+ }
42
+ if (config.hooks === undefined) config.hooks = {};
43
+ if (!config.hooks || typeof config.hooks !== 'object' || Array.isArray(config.hooks)) {
44
+ throw new Error(file + ': hooks must be an object; nothing was changed');
45
+ }
46
+ for (const [event, entries] of Object.entries(config.hooks)) {
47
+ if (!Array.isArray(entries)) {
48
+ throw new Error(file + ': hooks.' + event + ' must be an array; nothing was changed');
49
+ }
50
+ }
51
+ return config;
52
+ }
53
+
54
+ function writeConfig(file, config) {
55
+ fs.mkdirSync(path.dirname(file), { recursive: true });
56
+ fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n', 'utf8');
57
+ }
58
+
59
+ // The template's PONYTAIL_DIR placeholder becomes this checkout's absolute
60
+ // path. Forward slashes run unchanged under cmd, PowerShell and bash, and the
61
+ // same allowlist that guards the statusline snippet keeps shell metacharacters
62
+ // out of the command string.
63
+ function ponytailEntries() {
64
+ const root = ROOT.replace(/\\/g, '/');
65
+ if (!isShellSafe(root)) {
66
+ throw new Error('ponytail is checked out at a path with shell metacharacters (' + ROOT +
67
+ '); move it, or copy hooks/cursor-hooks.json by hand and quote the path for your shell');
68
+ }
69
+ const template = JSON.parse(fs.readFileSync(TEMPLATE, 'utf8'));
70
+ const hooks = {};
71
+ for (const [event, entries] of Object.entries(template.hooks)) {
72
+ hooks[event] = entries.map((entry) => ({ ...entry, command: entry.command.replace(/PONYTAIL_DIR/g, root) }));
73
+ }
74
+ return hooks;
75
+ }
76
+
77
+ function stripPonytail(config) {
78
+ for (const [event, entries] of Object.entries(config.hooks)) {
79
+ if (!Array.isArray(entries)) continue;
80
+ const kept = entries.filter((entry) => !isPonytailHook(entry));
81
+ if (kept.length) config.hooks[event] = kept;
82
+ else delete config.hooks[event];
83
+ }
84
+ }
85
+
86
+ function install(scope) {
87
+ const file = hooksPath(scope);
88
+ const config = readConfig(file);
89
+ if (config.version === undefined) config.version = 1;
90
+ // Re-running replaces stale ponytail entries instead of duplicating them.
91
+ stripPonytail(config);
92
+ for (const [event, entries] of Object.entries(ponytailEntries())) {
93
+ config.hooks[event] = [...(config.hooks[event] || []), ...entries];
94
+ }
95
+ writeConfig(file, config);
96
+ return file;
97
+ }
98
+
99
+ // Returns the file it changed, or null when there was nothing of ponytail's in it.
100
+ function uninstall(scope) {
101
+ const file = hooksPath(scope);
102
+ if (!fs.existsSync(file)) return null;
103
+ const config = readConfig(file);
104
+ const before = JSON.stringify(config);
105
+ stripPonytail(config);
106
+ if (JSON.stringify(config) === before) return null;
107
+ const otherKeys = Object.keys(config).filter((k) => k !== 'version' && k !== 'hooks');
108
+ if (Object.keys(config.hooks).length === 0 && otherKeys.length === 0) {
109
+ // Only ponytail lived here: drop the file rather than leave an empty husk.
110
+ fs.unlinkSync(file);
111
+ } else {
112
+ writeConfig(file, config);
113
+ }
114
+ return file;
115
+ }
116
+
117
+ if (require.main === module) {
118
+ const args = process.argv.slice(2);
119
+ const action = args[0];
120
+ const scope = args.includes('--project') ? 'project' : 'user';
121
+ try {
122
+ if (action === 'install') {
123
+ const file = install(scope);
124
+ console.log('Installed ponytail hooks in ' + file);
125
+ console.log('Cursor reloads hooks.json on save; start a new chat to activate.');
126
+ } else if (action === 'uninstall') {
127
+ const file = uninstall(scope);
128
+ console.log(file
129
+ ? 'Removed ponytail hooks from ' + file
130
+ : 'No ponytail hooks in ' + hooksPath(scope));
131
+ } else {
132
+ console.error('usage: node scripts/cursor-hooks.js install|uninstall [--project]');
133
+ process.exit(1);
134
+ }
135
+ } catch (e) {
136
+ if (e instanceof SyntaxError) {
137
+ console.error(hooksPath(scope) + ' is not valid JSON; nothing was changed, fix it by hand (' + e.message + ')');
138
+ } else {
139
+ console.error(e.message);
140
+ }
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ module.exports = { hooksPath, install, isPonytailHook, uninstall };
@@ -0,0 +1,42 @@
1
+ // scripts/generate-chillslop.mjs
2
+ import fs from 'node:fs';
3
+
4
+ let text = fs.readFileSync('antislop.raw.md', 'utf8');
5
+
6
+ text = text.replace(
7
+ /# antislop\n\n> Anti Slop: Rules for AI Coding Agents/g,
8
+ '# ChillSlop 2.0: Anti-Slop & Pragmatic Senior Dev Standard\n\n> Dual Core: Anti-Slop (miqdadbadjuber) + Ponytail (dietrichgebert) + ChillRewind Time-Travel'
9
+ );
10
+
11
+ text = text.replace(/antislop/g, 'chill-agent');
12
+ text = text.replace(/Anti Slop/g, 'ChillAgent Anti-Slop & Ponytail Core');
13
+
14
+ const ponytailHeader = `
15
+ ## The Pragmatic Senior Dev Creed (Ponytail Synthesis)
16
+
17
+ > **"The best code is the code you never wrote. The best design is the interface that gets out of the user's way."**
18
+
19
+ Active for the whole session until the user says "stop chill" or "normal mode".
20
+ Switch intensity mode: \`/chill [lite|full|ultra]\`.
21
+
22
+ ### The 6-Step Minimalist Execution Ladder
23
+ Take the first option that completely solves the problem:
24
+ 1. **Does it need to exist?** Skip features, options, and flexibility nobody asked for.
25
+ 2. **Already in this codebase?** Use existing helpers, components, and services.
26
+ 3. **Standard library or native platform?** Reach for \`node:*\`, native \`fetch\`, CSS Grid, native \`<dialog>\`.
27
+ 4. **Already-installed dependency?** Use what exists in \`package.json\`. Never add an npm package for 10 lines.
28
+ 5. **Can it be a single readable line?** Write the single line.
29
+ 6. **Otherwise:** The minimum code that works reliably.
30
+
31
+ Be lazy about the solution, never about the change itself: finish every caller, test, fixture, and edge case your change touches. Deletion beats addition.
32
+ `;
33
+
34
+ text = text.replace('## Core Principle', ponytailHeader + '\n## Core Principle');
35
+
36
+ fs.writeFileSync('chillslop.md', text, 'utf8');
37
+ fs.writeFileSync('antislop.md', text, 'utf8');
38
+ fs.writeFileSync('rules/chillslop.md', text, 'utf8');
39
+ fs.writeFileSync('rules/antislop.md', text, 'utf8');
40
+ fs.writeFileSync('rules/chillslop.mdc', text, 'utf8');
41
+
42
+ console.log('Successfully generated full-length master rules (chars: ' + text.length + ')');
@@ -0,0 +1,149 @@
1
+ // scripts/generate-examples.mjs
2
+ import fs from 'node:fs';
3
+
4
+ const examples = [
5
+ {
6
+ file: 'examples/README.md',
7
+ content: `# ChillAgent Examples & Empirical Diff Comparisons
8
+
9
+ This folder documents verbatim model outputs across benchmark runs comparing generic AI generation (bloat, slop, unnecessary dependencies) with **ChillAgent (Anti-Slop 2.0 + Ponytail)**.
10
+
11
+ ## Categories
12
+ - **Logic & Minimalism:** CSV parsing, debounce, deep cloning, group by, URL parsing, rate limiting.
13
+ - **UI & Accessibility:** Modal dialogs, countdown timers, infinite scroll, typography contrast.
14
+ - **Visual & Copywriting:** Eliminating generic purple gradients and robotic marketing buzzwords.
15
+ `
16
+ },
17
+ {
18
+ file: 'examples/csv-sum.md',
19
+ content: `# CSV Sum
20
+
21
+ **Task:** "Write Python code that reads sales.csv and sums the 'amount' column."
22
+
23
+ ## Without ChillAgent (20 lines, 1 heavy dependency)
24
+ \`\`\`python
25
+ import pandas as pd
26
+
27
+ # Read the CSV file into a DataFrame
28
+ df = pd.read_csv('sales.csv')
29
+
30
+ # Sum the amount column
31
+ total_amount = df['amount'].sum()
32
+
33
+ # Print the result
34
+ print(f"Total sales amount: {total_amount}")
35
+ \`\`\`
36
+ *Bloat:* Forces 35MB \`pandas\` dependency for a simple column sum.
37
+
38
+ ## With ChillAgent (4 lines, 0 dependencies)
39
+ \`\`\`python
40
+ import csv
41
+
42
+ with open('sales.csv') as f:
43
+ print(sum(float(row['amount']) for row in csv.DictReader(f)))
44
+ \`\`\`
45
+ *Result:* 0 dependencies, instant execution, standard library only.
46
+ `
47
+ },
48
+ {
49
+ file: 'examples/debounce.md',
50
+ content: `# Debounce Utility
51
+
52
+ **Task:** "Write a debounce function in TypeScript."
53
+
54
+ ## Without ChillAgent (25 lines + lodash dependency)
55
+ \`\`\`typescript
56
+ import debounce from 'lodash/debounce';
57
+ export const debouncedSearch = debounce((query: string) => {
58
+ api.search(query);
59
+ }, 300);
60
+ \`\`\`
61
+
62
+ ## With ChillAgent (6 lines, standard native timer)
63
+ \`\`\`typescript
64
+ export function debounce<T extends (...args: any[]) => void>(fn: T, ms = 300) {
65
+ let timer: ReturnType<typeof setTimeout>;
66
+ return (...args: Parameters<T>) => {
67
+ clearTimeout(timer);
68
+ timer = setTimeout(() => fn(...args), ms);
69
+ };
70
+ }
71
+ \`\`\`
72
+ `
73
+ },
74
+ {
75
+ file: 'examples/deep-clone.md',
76
+ content: `# Deep Clone
77
+
78
+ **Task:** "Deep clone a JavaScript object in Node 18+."
79
+
80
+ ## Without ChillAgent
81
+ \`\`\`javascript
82
+ import cloneDeep from 'lodash/cloneDeep';
83
+ const copy = cloneDeep(source);
84
+ \`\`\`
85
+
86
+ ## With ChillAgent (1 line native standard library)
87
+ \`\`\`javascript
88
+ const copy = structuredClone(source);
89
+ \`\`\`
90
+ `
91
+ },
92
+ {
93
+ file: 'examples/modal-dialog.md',
94
+ content: `# Modal Dialog Component
95
+
96
+ **Task:** "Create an accessible modal dialog in vanilla HTML/CSS/JS."
97
+
98
+ ## Without ChillAgent (120 lines, custom backdrop, custom focus trap, custom scroll locks)
99
+ Manual z-index soup, forgotten Escape key handlers, broken keyboard traps.
100
+
101
+ ## With ChillAgent (12 lines using native HTML <dialog>)
102
+ \`\`\`html
103
+ <dialog id="confirmModal" style="border: 1px solid #26262E; border-radius: 8px; background: #111114; color: #F4F4F6; padding: 20px;">
104
+ <form method="dialog">
105
+ <p>Confirm irreversible session fork?</p>
106
+ <button value="cancel">Cancel</button>
107
+ <button value="confirm" style="background: #818CF8; color: #09090B;">Fork</button>
108
+ </form>
109
+ </dialog>
110
+ <button onclick="document.getElementById('confirmModal').showModal()">Open</button>
111
+ \`\`\`
112
+ *Features:* Built-in backdrop, native Escape key closing, native focus trapping, zero external libraries.
113
+ `
114
+ },
115
+ {
116
+ file: 'examples/anti-slop-ui.md',
117
+ content: `# Anti-Slop UI Transformation
118
+
119
+ ## The Generic AI Default (Rejected as Slop)
120
+ - Background: \`bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500\`
121
+ - Card: \`rounded-3xl shadow-2xl backdrop-blur-xl p-8 bg-white/10\`
122
+ - Button: Pill-shaped glowing cyan bubble with rocket emoji \`🚀\`
123
+ - Heading: *"Unleash the Power of Next-Gen AI"*
124
+
125
+ ## The ChillAgent Standard (Accepted High-Craft)
126
+ - Background: Deep obsidian carbon \`#09090B\`
127
+ - Card: Monochromatic surface \`#111114\` with 1px hairline border \`#26262E\` and 8px radius
128
+ - Numbers: Tabular monospace font \`JetBrains Mono\`
129
+ - Accent: Single semantic indicator (Emerald \`#10B981\` for active status)
130
+ - Heading: Direct domain statement: *"Deterministic time-travel debugger for AI agents."*
131
+ `
132
+ },
133
+ {
134
+ file: 'examples/anti-slop-copy.md',
135
+ content: `# Anti-Slop Copywriting Transformation
136
+
137
+ ## AI Slop Copy (Banned)
138
+ > *"Unleash the revolutionary power of our cutting-edge AI debugger! Seamlessly supercharge your development workflow with next-generation intelligence. Trusted by 10,000+ happy developers across the globe! ✨"*
139
+
140
+ ## ChillAgent Clean Copy (Approved)
141
+ > *"ChillRewind is a zero-dependency local proxy running on \`node:sqlite\` in WAL mode. It prunes stale tool outputs over 1.5KB and halts agent runs when the same tool signature repeats 3 consecutive times."*
142
+ `
143
+ }
144
+ ];
145
+
146
+ for (const ex of examples) {
147
+ fs.writeFileSync(ex.file, ex.content.trim() + '\n', 'utf8');
148
+ console.log('Saved', ex.file);
149
+ }
@@ -0,0 +1,108 @@
1
+ // scripts/generate-plugin-doors.mjs
2
+ import fs from 'node:fs';
3
+
4
+ const manifest = {
5
+ name: "chill-agent",
6
+ displayName: "ChillAgent (Anti-Slop 2.0 + Ponytail Senior Dev)",
7
+ version: "1.2.0",
8
+ description: "Master Negative Filter, Anti-Slop, and Pragmatic Senior Dev Standard. Combines ruthless YAGNI minimalism with Linear-grade visual engineering.",
9
+ author: {
10
+ name: "sann"
11
+ },
12
+ homepage: "https://github.com/sann/chill-agent",
13
+ repository: "https://github.com/sann/chill-agent",
14
+ license: "MIT",
15
+ keywords: [
16
+ "anti-slop",
17
+ "ponytail",
18
+ "design-rules",
19
+ "minimalism",
20
+ "agent-skills",
21
+ "yagni",
22
+ "ui",
23
+ "accessibility"
24
+ ],
25
+ skills: [
26
+ "./skills/chill-slop/",
27
+ "./skills/chill-slop-ui/",
28
+ "./skills/chill-slop-code/",
29
+ "./skills/chill-slop-copywriting/",
30
+ "./skills/chill-slop-human/",
31
+ "./skills/chill-slop-mobile/",
32
+ "./skills/chill-audit/",
33
+ "./skills/chill-review/",
34
+ "./skills/chill-debt/",
35
+ "./skills/chill-gain/",
36
+ "./skills/chill-help/"
37
+ ]
38
+ };
39
+
40
+ // 1. Antigravity plugin.json
41
+ fs.writeFileSync('plugin.json', JSON.stringify({ name: "chill-agent" }, null, 2), 'utf8');
42
+
43
+ // 2. Universal plugin.yaml
44
+ fs.writeFileSync('plugin.yaml', `name: chill-agent
45
+ version: 1.2.0
46
+ description: ChillAgent Anti-Slop 2.0 & Pragmatic Senior Dev Standard
47
+ skills:
48
+ - ./skills/chill-slop/
49
+ - ./skills/chill-slop-ui/
50
+ - ./skills/chill-slop-code/
51
+ - ./skills/chill-slop-copywriting/
52
+ - ./skills/chill-slop-human/
53
+ - ./skills/chill-slop-mobile/
54
+ - ./skills/chill-audit/
55
+ - ./skills/chill-review/
56
+ `, 'utf8');
57
+
58
+ // 3. gemini-extension.json
59
+ fs.writeFileSync('gemini-extension.json', JSON.stringify({
60
+ name: "chill-agent",
61
+ version: "1.2.0",
62
+ description: "ChillAgent Anti-Slop 2.0 & Pragmatic Senior Dev Mode. Forces smallest working diff, YAGNI, standard library first, zero AI slop.",
63
+ contextFileName: "AGENTS.md"
64
+ }, null, 2), 'utf8');
65
+
66
+ // 4. opencode.json
67
+ fs.writeFileSync('opencode.json', JSON.stringify({
68
+ name: "chill-agent",
69
+ version: "1.2.0",
70
+ description: "ChillAgent Anti-Slop & Ponytail Engine for OpenCode",
71
+ skills: manifest.skills
72
+ }, null, 2), 'utf8');
73
+
74
+ // 5. .claude-plugin/plugin.json
75
+ fs.writeFileSync('.claude-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
76
+
77
+ // 6. .cursor-plugin/plugin.json
78
+ fs.writeFileSync('.cursor-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
79
+
80
+ // 7. .cursor/rules/chill-agent.mdc
81
+ fs.writeFileSync('.cursor/rules/chill-agent.mdc', `---
82
+ description: ChillAgent Anti-Slop 2.0 & Pragmatic Senior Dev Standard
83
+ globs: *
84
+ ---
85
+ ` + fs.readFileSync('skills/chill-slop/SKILL.md', 'utf8'), 'utf8');
86
+
87
+ // 8. .windsurf/rules/chill-agent.md
88
+ fs.writeFileSync('.windsurf/rules/chill-agent.md', fs.readFileSync('skills/chill-slop/SKILL.md', 'utf8'), 'utf8');
89
+
90
+ // 9. .cline-plugin/plugin.json & .clinerules
91
+ fs.writeFileSync('.cline-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
92
+ fs.writeFileSync('.clinerules/chill-agent', fs.readFileSync('skills/chill-slop/SKILL.md', 'utf8'), 'utf8');
93
+
94
+ // 10. .codex-plugin/plugin.json & .agents/plugins/marketplace.json
95
+ fs.writeFileSync('.codex-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
96
+ fs.writeFileSync('.agents/plugins/marketplace.json', JSON.stringify({
97
+ plugins: [manifest]
98
+ }, null, 2), 'utf8');
99
+
100
+ // 11. .kimi-plugin, .omp-plugin, .devin-plugin, .grok-plugin, .qoder-plugin
101
+ fs.writeFileSync('.kimi-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
102
+ fs.writeFileSync('.omp-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
103
+ fs.writeFileSync('.devin-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
104
+ fs.writeFileSync('.grok-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
105
+ fs.writeFileSync('.qoder-plugin/plugin.json', JSON.stringify(manifest, null, 2), 'utf8');
106
+ fs.writeFileSync('.qoder/rules/chill-agent.md', fs.readFileSync('skills/chill-slop/SKILL.md', 'utf8'), 'utf8');
107
+
108
+ console.log('Successfully generated all multi-agent plugin doors and manifests!');
@@ -0,0 +1,65 @@
1
+ // scripts/sync-ponytail.mjs
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const filesToFetch = [
6
+ '__init__.py',
7
+ '.agents/rules/ponytail.md',
8
+ '.claude-plugin/marketplace.json',
9
+ '.clinerules/ponytail.md',
10
+ '.github/FUNDING.yml',
11
+ '.github/copilot-instructions.md',
12
+ '.github/plugin/marketplace.json',
13
+ '.github/plugin/plugin.json',
14
+ '.github/workflows/publish.yml',
15
+ '.github/workflows/test.yml',
16
+ '.grok-plugin/marketplace.json',
17
+ '.kiro/steering/ponytail.md',
18
+ '.qoder/rules/ponytail.md',
19
+ '.windsurf/rules/ponytail.md',
20
+ '.cursor/rules/ponytail.mdc',
21
+ '.opencode/command/ponytail-audit.md',
22
+ '.opencode/command/ponytail-debt.md',
23
+ '.opencode/command/ponytail-gain.md',
24
+ '.opencode/command/ponytail-help.md',
25
+ '.opencode/command/ponytail-review.md',
26
+ '.opencode/command/ponytail.md',
27
+ '.opencode/plugins/index.js',
28
+ '.opencode/plugins/ponytail-frontmatter.cjs',
29
+ '.opencode/plugins/ponytail.mjs',
30
+ 'hooks/ponytail-activate.js',
31
+ 'hooks/ponytail-mode-tracker.js',
32
+ 'hooks/ponytail-instructions.js',
33
+ 'hooks/ponytail-runtime.js',
34
+ 'hooks/ponytail-statusline.ps1',
35
+ 'hooks/ponytail-statusline.sh',
36
+ 'pi-extension/index.js',
37
+ 'pi-extension/package.json',
38
+ 'scripts/check-rule-copies.js',
39
+ 'scripts/check-versions.js',
40
+ 'scripts/cursor-hooks.js',
41
+ 'scripts/uninstall.js'
42
+ ];
43
+
44
+ async function run() {
45
+ console.log('Fetching and syncing full ponytail files...');
46
+ for (const rel of filesToFetch) {
47
+ try {
48
+ const url = `https://raw.githubusercontent.com/DietrichGebert/ponytail/main/${rel}`;
49
+ const res = await fetch(url);
50
+ if (res.ok) {
51
+ const text = await res.text();
52
+ const dir = path.dirname(rel);
53
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
54
+ fs.writeFileSync(rel, text, 'utf8');
55
+ console.log(`✓ Synced: ${rel} (${text.length} B)`);
56
+ } else {
57
+ console.warn(`✗ Not found (${res.status}): ${rel}`);
58
+ }
59
+ } catch (err) {
60
+ console.error(`Error on ${rel}:`, err.message);
61
+ }
62
+ }
63
+ }
64
+
65
+ run();
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // ponytail — removes state ponytail wrote outside the plugin's own files:
3
+ // the mode flag, the config file, the statusline script copy, the
4
+ // statusLine entry it added to settings.json, and its entries in
5
+ // ~/.cursor/hooks.json. Plugin files themselves are removed by each host's
6
+ // own uninstall command (see README); this only cleans up what those
7
+ // commands can't see.
8
+
9
+ const fs = require('fs');
10
+ const os = require('os');
11
+ const path = require('path');
12
+ const { getConfigPath, getClaudeDir, getQoderDir } = require('../hooks/ponytail-config');
13
+ const cursorHooks = require('./cursor-hooks');
14
+
15
+ const STATUSLINE_SCRIPT = /(?:^|[\s"'\\/])ponytail-statusline\.(?:sh|ps1)(?=$|[\s"'])/;
16
+
17
+ function removeIfExists(filePath, label) {
18
+ try {
19
+ fs.unlinkSync(filePath);
20
+ console.log(`Removed ${label}: ${filePath}`);
21
+ } catch (e) {
22
+ if (e.code !== 'ENOENT') throw e;
23
+ }
24
+ }
25
+
26
+ removeIfExists(path.join(getClaudeDir(), '.ponytail-active'), 'mode flag');
27
+ removeIfExists(path.join(getClaudeDir(), '.ponytail-statusline-nudged'), 'statusline nudge flag');
28
+ removeIfExists(path.join(os.homedir(), '.cursor', '.ponytail-active'), 'Cursor mode flag');
29
+ removeIfExists(path.join(getQoderDir(), '.ponytail-active'), 'Qoder mode flag');
30
+ for (const dir of [path.join(getClaudeDir(), 'ponytail-modes'), path.join(os.homedir(), '.cursor', 'ponytail-modes'), path.join(process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy'), 'ponytail-modes')]) {
31
+ if (fs.existsSync(dir)) {
32
+ fs.rmSync(dir, { recursive: true });
33
+ console.log(`Removed per-project mode flags: ${dir}`);
34
+ }
35
+ }
36
+ removeIfExists(path.join(process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), '.codebuddy'), '.ponytail-active'), 'CodeBuddy mode flag');
37
+ removeIfExists(path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode', '.ponytail-active'), 'OpenCode mode flag');
38
+ removeIfExists(getConfigPath(), 'config file');
39
+ // Statusline script copies the activate hook keeps in the config dir (#1032).
40
+ for (const name of ['ponytail-statusline.sh', 'ponytail-statusline.ps1']) {
41
+ removeIfExists(path.join(getClaudeDir(), name), 'statusline script');
42
+ }
43
+
44
+ // Cursor hooks (#817): drop only ponytail's entries from ~/.cursor/hooks.json,
45
+ // keep every other hook the user configured there.
46
+ try {
47
+ const hooksFile = cursorHooks.uninstall('user');
48
+ if (hooksFile) console.log(`Removed ponytail hooks from ${hooksFile}`);
49
+ } catch (e) {
50
+ if (e instanceof SyntaxError) {
51
+ // ponytail: malformed hooks.json — can't safely edit it; leave intact, warn
52
+ console.warn(`~/.cursor/hooks.json is malformed — could not remove the ponytail hook entries. Remove them manually from: ${cursorHooks.hooksPath('user')} (${e.message})`);
53
+ } else {
54
+ throw e;
55
+ }
56
+ }
57
+
58
+ const settingsPath = path.join(getClaudeDir(), 'settings.json');
59
+ try {
60
+ const raw = fs.readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
61
+ const settings = JSON.parse(raw);
62
+ const cmd = settings.statusLine && settings.statusLine.command;
63
+ // Only remove the parts ponytail owns. If the user combined statuslines
64
+ // (e.g. caveman && ponytail), keep the other plugin's command intact.
65
+ // ponytail: splits on && / ; to detect other segments — good enough; a user
66
+ // piping statuslines together is on their own.
67
+ if (typeof cmd === 'string' && STATUSLINE_SCRIPT.test(cmd)) {
68
+ const parts = cmd
69
+ .split(/&&|;/)
70
+ .map((s) => s.trim())
71
+ .filter(Boolean);
72
+ const others = parts.filter((s) => !STATUSLINE_SCRIPT.test(s));
73
+ if (others.length === 0) {
74
+ delete settings.statusLine;
75
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
76
+ console.log(`Removed ponytail statusLine entry from ${settingsPath}`);
77
+ } else {
78
+ settings.statusLine.command = others.join(' && ');
79
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
80
+ console.log(`Removed ponytail statusLine segment from ${settingsPath}`);
81
+ }
82
+ }
83
+ } catch (e) {
84
+ if (e.code === 'ENOENT') {
85
+ // no settings.json — nothing to clean
86
+ } else if (e instanceof SyntaxError) {
87
+ // ponytail: malformed settings.json — can't safely edit it; leave intact, warn
88
+ console.warn(`settings.json is malformed — could not remove the ponytail statusLine entry. Remove it manually from: ${settingsPath} (${e.message})`);
89
+ } else {
90
+ throw e;
91
+ }
92
+ }
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: ponytail
3
+ description: >
4
+ Lazy senior dev mode: the smallest change that fully solves the task, and a
5
+ reply a busy human understands in one read. Use on any coding task (writing,
6
+ fixing, refactoring, reviewing, choosing dependencies) and when the user says
7
+ "ponytail", "be lazy", "simplest solution", "yagni", or complains about
8
+ over-engineering or bloat. Levels: lite, full (default), ultra.
9
+ argument-hint: "[lite|full|ultra|off]"
10
+ license: MIT
11
+ ---
12
+
13
+ # Ponytail
14
+
15
+ You are a lazy senior developer. The best code is the code never written. You solve the whole problem with the least new code. End your reply with one or two lines: what you skipped or did not check, and any risk the user must know.
16
+
17
+ Active for the whole session until the user says "stop ponytail" or "normal mode". Switch level: `/ponytail lite|full|ultra`.
18
+
19
+ ## Before you write
20
+
21
+ Read the task and the code it touches. List every place your change must reach: callers, tests, fixtures, config, exports. Check what your change could break for users: data it would destroy or expose, callers that stop working. That is scope. Extra features are not.
22
+
23
+ ## The smallest complete change
24
+
25
+ Take the first option that fully works:
26
+
27
+ 1. Does it need to exist? Skip features, options and flexibility nobody asked for, and name them in one line. A vague request ("build me X") gets the smallest version that does the core job.
28
+ 2. Already in this codebase (a helper, component, service, pattern)? Use it the way the surrounding code does.
29
+ 3. Standard library or a platform feature? Use it, unless the project has its own. A house component beats a native widget.
30
+ 4. An installed dependency? Use it. Never add a dependency for a few lines.
31
+ 5. Can it be one line a reader gets at a glance? One line.
32
+ 6. Otherwise: the minimum code that works.
33
+
34
+ - Be lazy about the solution, never about the change itself: finish every part the task needs, including the callers, tests and fixtures your change breaks.
35
+ - No abstraction, wrapper, type conversion, option, config, boilerplate or "for later" code nobody asked for. Keep values in the form the platform already gives you. Deletion beats addition. Keep the structure the codebase already has: its layers, interfaces and conventions.
36
+ - The shortest working diff wins, once you know everything it must touch. A one-liner that needs decoding is not short.
37
+ - Comment only the why the code cannot show, in one line.
38
+ - Bug fix: before you edit, grep every caller of the function you touch, then fix the root cause once in the shared code.
39
+ - Code you move or merge keeps its error handling and validation.
40
+ - Between options of equal size, take the one that is correct on edge cases.
41
+ - Lazy code without its check is unfinished: new non-trivial logic (a branch, a loop, a parser, money or security, or a whole new script or app) leaves one small test or an assert-based self-check. Trivial changes need none.
42
+ - A shortcut with a known limit gets a code comment in this form: `shortcut: <the limit>, <when to upgrade>`.
43
+
44
+ Never cut: validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs, anything the user asked for.
45
+
46
+ ## Levels
47
+
48
+ | Level | Behavior |
49
+ |-------|----------|
50
+ | **lite** | Build what was asked. Name the smaller option in one line and let the user pick. |
51
+ | **full** | The rules above. Default. |
52
+ | **ultra** | Also question the request: before building, push back on any part the need does not justify. |