chill-agent 1.5.0 → 1.6.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 (187) hide show
  1. package/.claude-plugin/contrast-mcp-launcher.mjs +31 -0
  2. package/.claude-plugin/plugin.json +8 -3
  3. package/.cline-plugin/plugin.js +6 -0
  4. package/.codex-plugin/plugin.json +4 -3
  5. package/.cursor-plugin/marketplace.json +16 -0
  6. package/.cursor-plugin/plugin.json +2 -2
  7. package/.devin-plugin/plugin.json +2 -2
  8. package/.kimi-plugin/plugin.json +11 -29
  9. package/.omp-plugin/marketplace.json +15 -0
  10. package/.openclaw/skills/ponytail/SKILL.md +2 -7
  11. package/.openclaw/skills/ponytail-audit/SKILL.md +3 -6
  12. package/.openclaw/skills/ponytail-debt/SKILL.md +3 -4
  13. package/.openclaw/skills/ponytail-gain/SKILL.md +3 -4
  14. package/.openclaw/skills/ponytail-help/SKILL.md +3 -3
  15. package/.openclaw/skills/ponytail-review/SKILL.md +3 -6
  16. package/.opencode/plugins/ponytail.mjs +1 -1
  17. package/.qoder-plugin/plugin.json +12 -26
  18. package/AGENTS.md +32 -19
  19. package/__init__.py +1 -1
  20. package/assets/antislop-banner.png +0 -0
  21. package/assets/band/band.png +0 -0
  22. package/assets/band/board.png +0 -0
  23. package/assets/benchmark-3model.svg +21 -0
  24. package/assets/benchmark-agentic.svg +62 -0
  25. package/assets/codex-icon.png +0 -0
  26. package/assets/compare/code/after.webp +0 -0
  27. package/assets/compare/code/before.webp +0 -0
  28. package/assets/compare/text/after.webp +0 -0
  29. package/assets/compare/text/before.webp +0 -0
  30. package/assets/compare/ui/w-all.webp +0 -0
  31. package/assets/compare/ui/w-antislop.webp +0 -0
  32. package/assets/compare/ui/w-design.webp +0 -0
  33. package/assets/compare/ui/without.webp +0 -0
  34. package/assets/logo-dark.png +0 -0
  35. package/assets/logo-dark.svg +115 -0
  36. package/assets/logo-greenpt-dark.svg +27 -0
  37. package/assets/logo-greenpt.svg +27 -0
  38. package/assets/logo.png +0 -0
  39. package/assets/photoreunion-logo-dark.svg +4 -0
  40. package/assets/photoreunion-logo-light.svg +4 -0
  41. package/assets/profile.png +0 -0
  42. package/assets/retriever-logo-dark.svg +4 -0
  43. package/assets/retriever-logo-light.svg +4 -0
  44. package/assets/stories-banner.png +0 -0
  45. package/assets/v5/audit-es.png +0 -0
  46. package/assets/v5/audit-ja.png +0 -0
  47. package/assets/v5/audit-ko.png +0 -0
  48. package/assets/v5/audit-zh-CN.png +0 -0
  49. package/assets/v5/audit.png +0 -0
  50. package/assets/v5/beforeafter-es.png +0 -0
  51. package/assets/v5/beforeafter-ja.png +0 -0
  52. package/assets/v5/beforeafter-ko.png +0 -0
  53. package/assets/v5/beforeafter-zh-CN.png +0 -0
  54. package/assets/v5/beforeafter.png +0 -0
  55. package/assets/v5/chart-es.png +0 -0
  56. package/assets/v5/chart-ja.png +0 -0
  57. package/assets/v5/chart-ko.png +0 -0
  58. package/assets/v5/chart-zh-CN.png +0 -0
  59. package/assets/v5/chart.png +0 -0
  60. package/assets/v5/hero-es.jpg +0 -0
  61. package/assets/v5/hero-ja.jpg +0 -0
  62. package/assets/v5/hero-ko.jpg +0 -0
  63. package/assets/v5/hero-zh-CN.jpg +0 -0
  64. package/assets/v5/hero.jpg +0 -0
  65. package/assets/v5/ladder-es.png +0 -0
  66. package/assets/v5/ladder-ja.png +0 -0
  67. package/assets/v5/ladder-ko.png +0 -0
  68. package/assets/v5/ladder-zh-CN.png +0 -0
  69. package/assets/v5/ladder.png +0 -0
  70. package/assets/v5/review-es.png +0 -0
  71. package/assets/v5/review-ja.png +0 -0
  72. package/assets/v5/review-ko.png +0 -0
  73. package/assets/v5/review-zh-CN.png +0 -0
  74. package/assets/v5/review.png +0 -0
  75. package/assets/v5/tests-es.png +0 -0
  76. package/assets/v5/tests-ja.png +0 -0
  77. package/assets/v5/tests-ko.png +0 -0
  78. package/assets/v5/tests-zh-CN.png +0 -0
  79. package/assets/v5/tests.png +0 -0
  80. package/assets/waitlist-banner-es.png +0 -0
  81. package/assets/waitlist-banner-ja.png +0 -0
  82. package/assets/waitlist-banner-ko.png +0 -0
  83. package/assets/waitlist-banner-zh-CN.png +0 -0
  84. package/assets/waitlist-banner.png +0 -0
  85. package/benchmarks/.env.example +2 -0
  86. package/benchmarks/agentic/README.md +201 -0
  87. package/benchmarks/agentic/answer.py +288 -0
  88. package/benchmarks/agentic/compare.py +93 -0
  89. package/benchmarks/agentic/complete.py +178 -0
  90. package/benchmarks/agentic/judge.py +198 -0
  91. package/benchmarks/agentic/run.py +543 -0
  92. package/benchmarks/agentic/tasks.py +1544 -0
  93. package/benchmarks/arms/baseline.js +2 -0
  94. package/benchmarks/arms/caveman-SKILL.md +67 -0
  95. package/benchmarks/arms/caveman.js +8 -0
  96. package/benchmarks/arms/ponytail.js +8 -0
  97. package/benchmarks/behavior.js +54 -0
  98. package/benchmarks/behavior.yaml +40 -0
  99. package/benchmarks/correctness.js +291 -0
  100. package/benchmarks/loc.js +17 -19
  101. package/benchmarks/promptfooconfig.gemini.yaml +32 -0
  102. package/benchmarks/promptfooconfig.gpt-newest.yaml +33 -0
  103. package/benchmarks/promptfooconfig.yaml +41 -0
  104. package/benchmarks/results/2026-06-12-caveman-vs-ponytail.md +71 -0
  105. package/benchmarks/results/2026-06-12-v4-hardening-vs-caveman.md +125 -0
  106. package/benchmarks/results/2026-06-15-llama3.2-local.md +82 -0
  107. package/benchmarks/results/2026-06-16-correctness-gate-fix.md +113 -0
  108. package/benchmarks/results/2026-06-16-robustness-audit.md +135 -0
  109. package/benchmarks/results/2026-06-17-agentic-safety.md +164 -0
  110. package/benchmarks/results/2026-06-17-cost-verification.md +93 -0
  111. package/benchmarks/results/2026-06-18-agentic.md +219 -0
  112. package/benchmarks/results/2026-06-22-issue-245-217-comprehension.md +98 -0
  113. package/benchmarks/results/2026-10-07-agentic.md +186 -0
  114. package/cli/index.mjs +241 -0
  115. package/cli/lib/banner.mjs +21 -0
  116. package/cli/lib/install.mjs +498 -0
  117. package/cli/lib/settings.mjs +48 -0
  118. package/cli/package-lock.json +87 -0
  119. package/cli/package.json +42 -0
  120. package/cli/scripts/smoke-test.mjs +95 -0
  121. package/cli/scripts/smoke-worker.mjs +411 -0
  122. package/cli/scripts/sync-skills.mjs +81 -0
  123. package/examples/email-validation.md +156 -0
  124. package/examples/group-by.md +35 -0
  125. package/examples/infinite-scroll.md +58 -0
  126. package/examples/number-formatting.md +37 -0
  127. package/examples/rate-limit.md +272 -0
  128. package/examples/react-countdown.md +390 -0
  129. package/examples/url-params.md +41 -0
  130. package/gemini-extension.json +2 -2
  131. package/hooks/band/board.ts +560 -0
  132. package/hooks/band/logo.ts +3 -0
  133. package/hooks/band/metrics.ts +311 -0
  134. package/hooks/band/register.tsx +495 -0
  135. package/hooks/band/types.d.ts +34 -0
  136. package/hooks/claude-band.json +6 -0
  137. package/hooks/claude-codex-hooks.json +34 -4
  138. package/hooks/copilot-hooks.json +12 -2
  139. package/hooks/cursor-hooks.json +2 -2
  140. package/hooks/ponytail-band-import.js +274 -0
  141. package/hooks/ponytail-config.js +175 -0
  142. package/hooks/ponytail-map.js +111 -0
  143. package/hooks/ponytail-subagent.js +94 -0
  144. package/hooks/qoder-hooks.json +19 -4
  145. package/i18n/README.ko.md +230 -0
  146. package/package.json +12 -7
  147. package/pi-extension/test/extension.test.js +367 -0
  148. package/pi-extension/test/helpers.test.js +179 -0
  149. package/plugin.yaml +21 -12
  150. package/rules/antislop.mdc +18 -0
  151. package/scripts/build-openclaw-skills.js +60 -0
  152. package/scripts/check-repo.mjs +389 -0
  153. package/scripts/publish-openclaw-skills.js +75 -0
  154. package/scripts/skill-index.mjs +81 -0
  155. package/skills/antislop/SKILL.md +700 -0
  156. package/skills/antislop/VERSION +1 -0
  157. package/skills/antislop-code/SKILL.md +128 -0
  158. package/skills/antislop-copywriting/SKILL.md +407 -0
  159. package/skills/antislop-human/LICENSE-web-quality-skills +21 -0
  160. package/skills/antislop-human/SKILL.md +204 -0
  161. package/skills/antislop-human/contrast-check.py +168 -0
  162. package/skills/antislop-human/contrast-mcp.py +121 -0
  163. package/skills/antislop-human/references/A11Y-PATTERNS.md +249 -0
  164. package/skills/antislop-human/references/WCAG.md +127 -0
  165. package/skills/antislop-layoutmobile/SKILL.md +170 -0
  166. package/skills/antislop-ui/LICENSE-web-quality-skills +21 -0
  167. package/skills/antislop-ui/SKILL.md +344 -0
  168. package/skills/antislop-ui/references/CORE-WEB-VITALS.md +181 -0
  169. package/skills/antislop-ui/references/WEB-BASELINE.md +28 -0
  170. package/skills/plugin.json +5 -0
  171. package/src/package.json +3 -0
  172. package/tests/activation-map.test.js +63 -0
  173. package/tests/band-import.test.js +111 -0
  174. package/tests/band.test.tsx +520 -0
  175. package/tests/behavior.test.js +80 -0
  176. package/tests/gemini-extension.test.js +2 -2
  177. package/tests/grok-plugin.test.js +1 -1
  178. package/tests/hermes-plugin.test.js +1 -1
  179. package/tests/hooks-windows.test.js +205 -0
  180. package/tests/kimi-plugin.test.js +1 -1
  181. package/tests/map.test.js +30 -0
  182. package/tests/package-scripts.test.js +35 -0
  183. package/tests/package.test.js +23 -0
  184. package/tests/uninstall.test.js +202 -0
  185. /package/bin/{chill-agent.js → chill-agent.mjs} +0 -0
  186. /package/tests/{manifests.test.js → manifests.test.mjs} +0 -0
  187. /package/tests/{rules.test.js → rules.test.mjs} +0 -0
@@ -0,0 +1,390 @@
1
+ # Countdown Timer
2
+
3
+ **Task:** "Build me a countdown timer component in React that counts down from a given number of seconds."
4
+
5
+ Verbatim model output from a benchmark run, Claude Haiku 4.5, no-skill arm vs ponytail arm, temperature 1, source `benchmarks/output.json`. Reproduce: `npx promptfoo@latest eval -c benchmarks/promptfooconfig.yaml`.
6
+
7
+ ## Without Ponytail, 267 lines of code
8
+
9
+ # React Countdown Timer Component
10
+
11
+ Here's a complete countdown timer component with multiple variations:
12
+
13
+ ## Basic Countdown Timer
14
+
15
+ ```jsx
16
+ import { useState, useEffect } from 'react';
17
+
18
+ export function CountdownTimer({ initialSeconds = 60 }) {
19
+ const [seconds, setSeconds] = useState(initialSeconds);
20
+ const [isActive, setIsActive] = useState(false);
21
+
22
+ useEffect(() => {
23
+ let interval = null;
24
+
25
+ if (isActive && seconds > 0) {
26
+ interval = setInterval(() => {
27
+ setSeconds(seconds => seconds - 1);
28
+ }, 1000);
29
+ } else if (seconds === 0) {
30
+ setIsActive(false);
31
+ }
32
+
33
+ return () => clearInterval(interval);
34
+ }, [isActive, seconds]);
35
+
36
+ const toggle = () => setIsActive(!isActive);
37
+ const reset = () => {
38
+ setSeconds(initialSeconds);
39
+ setIsActive(false);
40
+ };
41
+
42
+ return (
43
+ <div className="countdown-timer">
44
+ <div className="timer-display">{seconds}s</div>
45
+ <div className="timer-controls">
46
+ <button onClick={toggle}>
47
+ {isActive ? 'Pause' : 'Start'}
48
+ </button>
49
+ <button onClick={reset}>Reset</button>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ## Advanced Timer with Formatted Display
57
+
58
+ ```jsx
59
+ import { useState, useEffect } from 'react';
60
+
61
+ export function AdvancedCountdownTimer({
62
+ initialSeconds = 3600,
63
+ onComplete = () => {},
64
+ showMilliseconds = false
65
+ }) {
66
+ const [timeLeft, setTimeLeft] = useState(initialSeconds);
67
+ const [isActive, setIsActive] = useState(false);
68
+
69
+ useEffect(() => {
70
+ let interval = null;
71
+
72
+ if (isActive && timeLeft > 0) {
73
+ interval = setInterval(() => {
74
+ setTimeLeft(prev => {
75
+ if (prev <= 1) {
76
+ setIsActive(false);
77
+ onComplete();
78
+ return 0;
79
+ }
80
+ return prev - 1;
81
+ });
82
+ }, 1000);
83
+ }
84
+
85
+ return () => clearInterval(interval);
86
+ }, [isActive, timeLeft, onComplete]);
87
+
88
+ const formatTime = (totalSeconds) => {
89
+ const hours = Math.floor(totalSeconds / 3600);
90
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
91
+ const seconds = totalSeconds % 60;
92
+
93
+ const parts = [];
94
+ if (hours > 0) parts.push(String(hours).padStart(2, '0'));
95
+ parts.push(String(minutes).padStart(2, '0'));
96
+ parts.push(String(seconds).padStart(2, '0'));
97
+
98
+ return parts.join(':');
99
+ };
100
+
101
+ const toggle = () => setIsActive(!isActive);
102
+ const reset = () => {
103
+ setTimeLeft(initialSeconds);
104
+ setIsActive(false);
105
+ };
106
+
107
+ const progressPercent = ((initialSeconds - timeLeft) / initialSeconds) * 100;
108
+
109
+ return (
110
+ <div className="advanced-timer">
111
+ <div className="timer-display">
112
+ {formatTime(timeLeft)}
113
+ </div>
114
+ <div className="progress-bar">
115
+ <div
116
+ className="progress-fill"
117
+ style={{ width: `${progressPercent}%` }}
118
+ />
119
+ </div>
120
+ <div className="timer-controls">
121
+ <button onClick={toggle} className="btn-primary">
122
+ {isActive ? '⏸ Pause' : '▶ Start'}
123
+ </button>
124
+ <button onClick={reset} className="btn-secondary">
125
+ ↻ Reset
126
+ </button>
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+ ```
132
+
133
+ ## Custom Hook Version
134
+
135
+ ```jsx
136
+ import { useState, useEffect, useCallback } from 'react';
137
+
138
+ export function useCountdown(initialSeconds) {
139
+ const [seconds, setSeconds] = useState(initialSeconds);
140
+ const [isActive, setIsActive] = useState(false);
141
+
142
+ useEffect(() => {
143
+ let interval = null;
144
+
145
+ if (isActive && seconds > 0) {
146
+ interval = setInterval(() => {
147
+ setSeconds(prev => prev - 1);
148
+ }, 1000);
149
+ } else if (seconds === 0 && isActive) {
150
+ setIsActive(false);
151
+ }
152
+
153
+ return () => clearInterval(interval);
154
+ }, [isActive, seconds]);
155
+
156
+ const start = useCallback(() => setIsActive(true), []);
157
+ const pause = useCallback(() => setIsActive(false), []);
158
+ const reset = useCallback(() => {
159
+ setSeconds(initialSeconds);
160
+ setIsActive(false);
161
+ }, [initialSeconds]);
162
+
163
+ return {
164
+ seconds,
165
+ isActive,
166
+ start,
167
+ pause,
168
+ reset,
169
+ setSeconds
170
+ };
171
+ }
172
+
173
+ // Usage
174
+ export function TimerWithHook() {
175
+ const { seconds, isActive, start, pause, reset } = useCountdown(60);
176
+
177
+ return (
178
+ <div>
179
+ <div>{seconds}s</div>
180
+ <button onClick={isActive ? pause : start}>
181
+ {isActive ? 'Pause' : 'Start'}
182
+ </button>
183
+ <button onClick={reset}>Reset</button>
184
+ </div>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ## Styled Component with Animations
190
+
191
+ ```jsx
192
+ import { useState, useEffect } from 'react';
193
+ import styled from 'styled-components';
194
+
195
+ const TimerContainer = styled.div`
196
+ display: flex;
197
+ flex-direction: column;
198
+ align-items: center;
199
+ gap: 20px;
200
+ padding: 30px;
201
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
202
+ border-radius: 15px;
203
+ color: white;
204
+ font-family: 'Arial', sans-serif;
205
+ `;
206
+
207
+ const Display = styled.div`
208
+ font-size: 48px;
209
+ font-weight: bold;
210
+ font-variant-numeric: tabular-nums;
211
+ animation: ${props => props.isWarning ? 'pulse 0.5s infinite' : 'none'};
212
+
213
+ @keyframes pulse {
214
+ 0%, 100% { opacity: 1; }
215
+ 50% { opacity: 0.5; }
216
+ }
217
+ `;
218
+
219
+ const Controls = styled.div`
220
+ display: flex;
221
+ gap: 10px;
222
+ `;
223
+
224
+ const Button = styled.button`
225
+ padding: 10px 20px;
226
+ font-size: 16px;
227
+ border: none;
228
+ border-radius: 8px;
229
+ cursor: pointer;
230
+ background-color: ${props => props.variant === 'danger' ? '#ff6b6b' : '#4ecdc4'};
231
+ color: white;
232
+ transition: all 0.3s ease;
233
+
234
+ &:hover {
235
+ transform: translateY(-2px);
236
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
237
+ }
238
+
239
+ &:active {
240
+ transform: translateY(0);
241
+ }
242
+ `;
243
+
244
+ export function StyledCountdownTimer({ initialSeconds = 60 }) {
245
+ const [seconds, setSeconds] = useState(initialSeconds);
246
+ const [isActive, setIsActive] = useState(false);
247
+
248
+ useEffect(() => {
249
+ let interval = null;
250
+
251
+ if (isActive && seconds > 0) {
252
+ interval = setInterval(() => {
253
+ setSeconds(prev => prev - 1);
254
+ }, 1000);
255
+ } else if (seconds === 0) {
256
+ setIsActive(false);
257
+ }
258
+
259
+ return () => clearInterval(interval);
260
+ }, [isActive, seconds]);
261
+
262
+ const isWarning = seconds <= 10 && seconds > 0;
263
+
264
+ return (
265
+ <TimerContainer>
266
+ <Display isWarning={isWarning}>{seconds}s</Display>
267
+ <Controls>
268
+ <Button onClick={() => setIsActive(!isActive)}>
269
+ {isActive ? 'Pause' : 'Start'}
270
+ </Button>
271
+ <Button
272
+ variant="danger"
273
+ onClick={() => {
274
+ setSeconds(initialSeconds);
275
+ setIsActive(false);
276
+ }}
277
+ >
278
+ Reset
279
+ </Button>
280
+ </Controls>
281
+ </TimerContainer>
282
+ );
283
+ }
284
+ ```
285
+
286
+ ## CSS Styling
287
+
288
+ ```css
289
+ .countdown-timer {
290
+ display: flex;
291
+ flex-direction: column;
292
+ align-items: center;
293
+ gap: 20px;
294
+ padding: 20px;
295
+ border: 2px solid #333;
296
+ border-radius: 10px;
297
+ background-color: #f5f5f5;
298
+ }
299
+
300
+ .timer-display {
301
+ font-size: 48px;
302
+ font-weight: bold;
303
+ font-variant-numeric: tabular-nums;
304
+ color: #333;
305
+ }
306
+
307
+ .timer-controls {
308
+ display: flex;
309
+ gap: 10px;
310
+ }
311
+
312
+ .timer-controls button {
313
+ padding: 10px 20px;
314
+ font-size: 16px;
315
+ border: none;
316
+ border-radius: 5px;
317
+ cursor: pointer;
318
+ background-color: #667eea;
319
+ color: white;
320
+ transition: background-color 0.3s;
321
+ }
322
+
323
+ .timer-controls button:hover {
324
+ background-color: #5568d3;
325
+ }
326
+
327
+ .progress-bar {
328
+ width: 100%;
329
+ height: 8px;
330
+ background-color: #e0e0e0;
331
+ border-radius: 4px;
332
+ overflow: hidden;
333
+ }
334
+
335
+ .progress-fill {
336
+ height: 100%;
337
+ background-color: #667eea;
338
+ transition: width 0.3s linear;
339
+ }
340
+ ```
341
+
342
+ ## Usage Examples
343
+
344
+ ```jsx
345
+ // Basic usage
346
+ <CountdownTimer initialSeconds={30} />
347
+
348
+ // Advanced with callback
349
+ <AdvancedCountdownTimer
350
+ initialSeconds={300}
351
+ onComplete={() => alert('Time\'s up!')}
352
+ />
353
+
354
+ // Using the hook
355
+ <TimerWithHook />
356
+
357
+ // Styled version
358
+ <StyledCountdownTimer initialSeconds={60} />
359
+ ```
360
+
361
+ ## Features
362
+
363
+ ✅ **Basic Timer** - Simple countdown with start/pause/reset
364
+ ✅ **Advanced Timer** - Formatted display (HH:MM:SS), progress bar, callbacks
365
+ ✅ **Custom Hook** - Reusable logic for any component
366
+ ✅ **Styled Components** - Beautiful UI with animations
367
+ ✅ **Warning State** - Visual feedback when time is running out
368
+ ✅ **Completion Callback** - Execute code when timer finishes
369
+
370
+ Choose the version that best fits your needs!
371
+
372
+ ## With Ponytail, 9 lines of code
373
+
374
+ ```jsx
375
+ export function CountdownTimer({ seconds }) {
376
+ const [remaining, setRemaining] = React.useState(seconds);
377
+
378
+ React.useEffect(() => {
379
+ if (remaining <= 0) return;
380
+ const timer = setInterval(() => setRemaining(r => r - 1), 1000);
381
+ return () => clearInterval(timer);
382
+ }, [remaining]);
383
+
384
+ return <div>{remaining}s</div>;
385
+ }
386
+ ```
387
+
388
+ Skipped: pause/resume, formatted display (mm:ss), sound on zero, styling, add when needed.
389
+
390
+ **267 → 9 lines of code**, same model, same prompt.
@@ -0,0 +1,41 @@
1
+ # URL Parameters
2
+
3
+ **Task:** "Parse and build URL query strings."
4
+
5
+ ## Without Ponytail
6
+
7
+ ```bash
8
+ npm install query-string
9
+ # 4.5 kB gzipped, 3.5M downloads/week
10
+ ```
11
+
12
+ ```js
13
+ import qs from "query-string";
14
+
15
+ // Parse
16
+ const params = qs.parse(location.search);
17
+ // → { page: "2", sort: "name", tags: ["js", "css"] }
18
+
19
+ // Build
20
+ const url = qs.stringify({ page: 2, sort: "name", tags: ["js", "css"] });
21
+ // → "page=2&sort=name&tags=js&tags=css"
22
+ ```
23
+
24
+ ## With Ponytail
25
+
26
+ ```js
27
+ // ponytail: URLSearchParams does this
28
+ const params = new URLSearchParams(location.search);
29
+
30
+ // Read
31
+ params.get("page"); // "2"
32
+ params.getAll("tags"); // ["js", "css"]
33
+
34
+ // Build
35
+ const out = new URLSearchParams({ page: 2, sort: "name" });
36
+ out.append("tags", "js");
37
+ out.append("tags", "css");
38
+ out.toString(); // "page=2&sort=name&tags=js&tags=css"
39
+ ```
40
+
41
+ **1 dependency → 0 dependencies.** `URLSearchParams` is in every browser and in Node.js since v10. It handles encoding, repeated keys, and iteration. The package was a polyfill for an API that has shipped everywhere for years.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chill-agent",
3
- "version": "1.2.0",
3
+ "version": "1.6.0",
4
4
  "description": "ChillAgent Anti-Slop 2.0 & Pragmatic Senior Dev Mode. Forces smallest working diff, YAGNI, standard library first, zero AI slop.",
5
5
  "contextFileName": "AGENTS.md"
6
- }
6
+ }