chill-agent 1.4.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.
- package/.agents/rules/ponytail.md +30 -0
- package/.claude-plugin/contrast-mcp-launcher.mjs +31 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +8 -3
- package/.cline-plugin/plugin.js +6 -0
- package/.clinerules/ponytail.md +30 -0
- package/.codex-plugin/plugin.json +4 -3
- package/.cursor/rules/ponytail.mdc +36 -0
- package/.cursor-plugin/marketplace.json +16 -0
- package/.cursor-plugin/plugin.json +2 -2
- package/.devin-plugin/plugin.json +2 -2
- package/.grok-plugin/marketplace.json +16 -0
- package/.kimi-plugin/plugin.json +11 -29
- package/.kiro/steering/ponytail.md +35 -0
- package/.omp-plugin/marketplace.json +15 -0
- package/.openclaw/skills/ponytail/SKILL.md +47 -0
- package/.openclaw/skills/ponytail-audit/SKILL.md +100 -0
- package/.openclaw/skills/ponytail-debt/SKILL.md +43 -0
- package/.openclaw/skills/ponytail-gain/SKILL.md +51 -0
- package/.openclaw/skills/ponytail-help/SKILL.md +76 -0
- package/.openclaw/skills/ponytail-review/SKILL.md +98 -0
- package/.opencode/command/ponytail-audit.md +5 -0
- package/.opencode/command/ponytail-debt.md +5 -0
- package/.opencode/command/ponytail-gain.md +5 -0
- package/.opencode/command/ponytail-help.md +5 -0
- package/.opencode/command/ponytail-review.md +5 -0
- package/.opencode/command/ponytail.md +5 -0
- package/.opencode/plugins/index.js +4 -0
- package/.opencode/plugins/ponytail-frontmatter.cjs +63 -0
- package/.opencode/plugins/ponytail.mjs +206 -0
- package/.qoder-plugin/plugin.json +12 -26
- package/.windsurf/rules/ponytail.md +30 -0
- package/AGENTS.md +32 -0
- package/__init__.py +228 -0
- package/assets/antislop-banner.png +0 -0
- package/assets/band/band.png +0 -0
- package/assets/band/board.png +0 -0
- package/assets/benchmark-3model.svg +21 -0
- package/assets/benchmark-agentic.svg +62 -0
- package/assets/codex-icon.png +0 -0
- package/assets/compare/code/after.webp +0 -0
- package/assets/compare/code/before.webp +0 -0
- package/assets/compare/text/after.webp +0 -0
- package/assets/compare/text/before.webp +0 -0
- package/assets/compare/ui/w-all.webp +0 -0
- package/assets/compare/ui/w-antislop.webp +0 -0
- package/assets/compare/ui/w-design.webp +0 -0
- package/assets/compare/ui/without.webp +0 -0
- package/assets/logo-dark.png +0 -0
- package/assets/logo-dark.svg +115 -0
- package/assets/logo-greenpt-dark.svg +27 -0
- package/assets/logo-greenpt.svg +27 -0
- package/assets/logo.png +0 -0
- package/assets/photoreunion-logo-dark.svg +4 -0
- package/assets/photoreunion-logo-light.svg +4 -0
- package/assets/profile.png +0 -0
- package/assets/retriever-logo-dark.svg +4 -0
- package/assets/retriever-logo-light.svg +4 -0
- package/assets/stories-banner.png +0 -0
- package/assets/v5/audit-es.png +0 -0
- package/assets/v5/audit-ja.png +0 -0
- package/assets/v5/audit-ko.png +0 -0
- package/assets/v5/audit-zh-CN.png +0 -0
- package/assets/v5/audit.png +0 -0
- package/assets/v5/beforeafter-es.png +0 -0
- package/assets/v5/beforeafter-ja.png +0 -0
- package/assets/v5/beforeafter-ko.png +0 -0
- package/assets/v5/beforeafter-zh-CN.png +0 -0
- package/assets/v5/beforeafter.png +0 -0
- package/assets/v5/chart-es.png +0 -0
- package/assets/v5/chart-ja.png +0 -0
- package/assets/v5/chart-ko.png +0 -0
- package/assets/v5/chart-zh-CN.png +0 -0
- package/assets/v5/chart.png +0 -0
- package/assets/v5/hero-es.jpg +0 -0
- package/assets/v5/hero-ja.jpg +0 -0
- package/assets/v5/hero-ko.jpg +0 -0
- package/assets/v5/hero-zh-CN.jpg +0 -0
- package/assets/v5/hero.jpg +0 -0
- package/assets/v5/ladder-es.png +0 -0
- package/assets/v5/ladder-ja.png +0 -0
- package/assets/v5/ladder-ko.png +0 -0
- package/assets/v5/ladder-zh-CN.png +0 -0
- package/assets/v5/ladder.png +0 -0
- package/assets/v5/review-es.png +0 -0
- package/assets/v5/review-ja.png +0 -0
- package/assets/v5/review-ko.png +0 -0
- package/assets/v5/review-zh-CN.png +0 -0
- package/assets/v5/review.png +0 -0
- package/assets/v5/tests-es.png +0 -0
- package/assets/v5/tests-ja.png +0 -0
- package/assets/v5/tests-ko.png +0 -0
- package/assets/v5/tests-zh-CN.png +0 -0
- package/assets/v5/tests.png +0 -0
- package/assets/waitlist-banner-es.png +0 -0
- package/assets/waitlist-banner-ja.png +0 -0
- package/assets/waitlist-banner-ko.png +0 -0
- package/assets/waitlist-banner-zh-CN.png +0 -0
- package/assets/waitlist-banner.png +0 -0
- package/benchmarks/.env.example +2 -0
- package/benchmarks/agentic/README.md +201 -0
- package/benchmarks/agentic/answer.py +288 -0
- package/benchmarks/agentic/compare.py +93 -0
- package/benchmarks/agentic/complete.py +178 -0
- package/benchmarks/agentic/judge.py +198 -0
- package/benchmarks/agentic/run.py +543 -0
- package/benchmarks/agentic/tasks.py +1544 -0
- package/benchmarks/arms/baseline.js +2 -0
- package/benchmarks/arms/caveman-SKILL.md +67 -0
- package/benchmarks/arms/caveman.js +8 -0
- package/benchmarks/arms/ponytail.js +8 -0
- package/benchmarks/behavior.js +54 -0
- package/benchmarks/behavior.yaml +40 -0
- package/benchmarks/correctness.js +291 -0
- package/benchmarks/loc.js +17 -19
- package/benchmarks/promptfooconfig.gemini.yaml +32 -0
- package/benchmarks/promptfooconfig.gpt-newest.yaml +33 -0
- package/benchmarks/promptfooconfig.yaml +41 -0
- package/benchmarks/results/2026-06-12-caveman-vs-ponytail.md +71 -0
- package/benchmarks/results/2026-06-12-v4-hardening-vs-caveman.md +125 -0
- package/benchmarks/results/2026-06-15-llama3.2-local.md +82 -0
- package/benchmarks/results/2026-06-16-correctness-gate-fix.md +113 -0
- package/benchmarks/results/2026-06-16-robustness-audit.md +135 -0
- package/benchmarks/results/2026-06-17-agentic-safety.md +164 -0
- package/benchmarks/results/2026-06-17-cost-verification.md +93 -0
- package/benchmarks/results/2026-06-18-agentic.md +219 -0
- package/benchmarks/results/2026-06-22-issue-245-217-comprehension.md +98 -0
- package/benchmarks/results/2026-10-07-agentic.md +186 -0
- package/cli/index.mjs +241 -0
- package/cli/lib/banner.mjs +21 -0
- package/cli/lib/install.mjs +498 -0
- package/cli/lib/settings.mjs +48 -0
- package/cli/package-lock.json +87 -0
- package/cli/package.json +42 -0
- package/cli/scripts/smoke-test.mjs +95 -0
- package/cli/scripts/smoke-worker.mjs +411 -0
- package/cli/scripts/sync-skills.mjs +81 -0
- package/commands/ponytail-audit.toml +2 -0
- package/commands/ponytail-debt.toml +2 -0
- package/commands/ponytail-gain.toml +2 -0
- package/commands/ponytail-help.toml +2 -0
- package/commands/ponytail-review.toml +2 -0
- package/commands/ponytail.toml +2 -0
- package/examples/email-validation.md +156 -0
- package/examples/group-by.md +35 -0
- package/examples/infinite-scroll.md +58 -0
- package/examples/number-formatting.md +37 -0
- package/examples/rate-limit.md +272 -0
- package/examples/react-countdown.md +390 -0
- package/examples/url-params.md +41 -0
- package/gemini-extension.json +2 -2
- package/hooks/band/board.ts +560 -0
- package/hooks/band/logo.ts +3 -0
- package/hooks/band/metrics.ts +311 -0
- package/hooks/band/register.tsx +495 -0
- package/hooks/band/types.d.ts +34 -0
- package/hooks/chill-statusline.ps1 +1 -1
- package/hooks/claude-band.json +6 -0
- package/hooks/claude-codex-hooks.json +34 -4
- package/hooks/copilot-hooks.json +12 -2
- package/hooks/cursor-hooks.json +2 -2
- package/hooks/ponytail-activate.js +201 -0
- package/hooks/ponytail-band-import.js +274 -0
- package/hooks/ponytail-config.js +175 -0
- package/hooks/ponytail-instructions.js +98 -0
- package/hooks/ponytail-map.js +111 -0
- package/hooks/ponytail-mode-tracker.js +216 -0
- package/hooks/ponytail-runtime.js +182 -0
- package/hooks/ponytail-statusline.ps1 +24 -0
- package/hooks/ponytail-statusline.sh +18 -0
- package/hooks/ponytail-subagent.js +94 -0
- package/hooks/qoder-hooks.json +19 -4
- package/i18n/README.ko.md +230 -0
- package/package.json +17 -6
- package/pi-extension/index.js +254 -0
- package/pi-extension/package.json +8 -0
- package/pi-extension/test/extension.test.js +367 -0
- package/pi-extension/test/helpers.test.js +179 -0
- package/plugin.yaml +21 -12
- package/rules/antislop.mdc +18 -0
- package/scripts/build-openclaw-skills.js +60 -0
- package/scripts/check-repo.mjs +389 -0
- package/scripts/check-rule-copies.js +74 -0
- package/scripts/check-versions.js +77 -0
- package/scripts/cursor-hooks.js +145 -0
- package/scripts/generate-chillslop.mjs +42 -0
- package/scripts/generate-examples.mjs +149 -0
- package/scripts/generate-plugin-doors.mjs +108 -0
- package/scripts/publish-openclaw-skills.js +75 -0
- package/scripts/skill-index.mjs +81 -0
- package/scripts/sync-ponytail.mjs +65 -0
- package/scripts/uninstall.js +92 -0
- package/skills/antislop/SKILL.md +700 -0
- package/skills/antislop/VERSION +1 -0
- package/skills/antislop-code/SKILL.md +128 -0
- package/skills/antislop-copywriting/SKILL.md +407 -0
- package/skills/antislop-human/LICENSE-web-quality-skills +21 -0
- package/skills/antislop-human/SKILL.md +204 -0
- package/skills/antislop-human/contrast-check.py +168 -0
- package/skills/antislop-human/contrast-mcp.py +121 -0
- package/skills/antislop-human/references/A11Y-PATTERNS.md +249 -0
- package/skills/antislop-human/references/WCAG.md +127 -0
- package/skills/antislop-layoutmobile/SKILL.md +170 -0
- package/skills/antislop-ui/LICENSE-web-quality-skills +21 -0
- package/skills/antislop-ui/SKILL.md +344 -0
- package/skills/antislop-ui/references/CORE-WEB-VITALS.md +181 -0
- package/skills/antislop-ui/references/WEB-BASELINE.md +28 -0
- package/skills/plugin.json +5 -0
- package/skills/ponytail/SKILL.md +52 -0
- package/skills/ponytail-audit/SKILL.md +103 -0
- package/skills/ponytail-debt/SKILL.md +44 -0
- package/skills/ponytail-gain/SKILL.md +52 -0
- package/skills/ponytail-help/SKILL.md +76 -0
- package/skills/ponytail-review/SKILL.md +101 -0
- package/src/package.json +3 -0
- package/tests/activation-map.test.js +63 -0
- package/tests/band-import.test.js +111 -0
- package/tests/band.test.tsx +520 -0
- package/tests/behavior.test.js +80 -0
- package/tests/commands.test.js +94 -0
- package/tests/copilot-plugin.test.js +35 -0
- package/tests/correctness.test.js +245 -0
- package/tests/cursor-hooks.test.js +343 -0
- package/tests/gemini-extension.test.js +91 -0
- package/tests/grok-plugin.test.js +39 -0
- package/tests/hermes-plugin.test.js +403 -0
- package/tests/hooks-windows.test.js +205 -0
- package/tests/hooks.test.js +1005 -0
- package/tests/kimi-plugin.test.js +25 -0
- package/tests/loc.test.js +18 -0
- package/tests/map.test.js +30 -0
- package/tests/openclaw-skills.test.js +26 -0
- package/tests/opencode-plugin.test.js +340 -0
- package/tests/package-scripts.test.js +35 -0
- package/tests/package.test.js +23 -0
- package/tests/qoder-plugin.test.js +84 -0
- package/tests/uninstall.test.js +202 -0
- /package/bin/{chill-agent.js → chill-agent.mjs} +0 -0
- /package/tests/{manifests.test.js → manifests.test.mjs} +0 -0
- /package/tests/{rules.test.js → rules.test.mjs} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Every ponytail command the pi extension registers must also ship as a
|
|
3
|
+
// file-based command for the hosts that need one: Claude Code (commands/*.toml,
|
|
4
|
+
// which Gemini CLI reuses) and OpenCode (.opencode/command/*.md). /ponytail-help
|
|
5
|
+
// was advertised in the README and the help card but missing both files; this
|
|
6
|
+
// guards that drift -- a registered command with no adapter file fails here.
|
|
7
|
+
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const { spawnSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const root = path.join(__dirname, '..');
|
|
15
|
+
|
|
16
|
+
// pi-extension registers the canonical command set.
|
|
17
|
+
const piSource = fs.readFileSync(path.join(root, 'pi-extension', 'index.js'), 'utf8');
|
|
18
|
+
const commands = [...piSource.matchAll(/registerCommand\(["']([\w-]+)["']/g)].map((m) => m[1]);
|
|
19
|
+
|
|
20
|
+
test('pi registers at least the base command', () => {
|
|
21
|
+
assert.ok(commands.includes('ponytail'), 'expected pi to register a ponytail command');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('every registered command ships a Claude commands/*.toml', () => {
|
|
25
|
+
for (const name of commands) {
|
|
26
|
+
assert.ok(
|
|
27
|
+
fs.existsSync(path.join(root, 'commands', `${name}.toml`)),
|
|
28
|
+
`missing commands/${name}.toml`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('every registered command ships an OpenCode .opencode/command/*.md', () => {
|
|
34
|
+
for (const name of commands) {
|
|
35
|
+
assert.ok(
|
|
36
|
+
fs.existsSync(path.join(root, '.opencode', 'command', `${name}.md`)),
|
|
37
|
+
`missing .opencode/command/${name}.md`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// /ponytail-debt documents one grep for the whole tree, shipped in four copies.
|
|
43
|
+
// It missed C-style /* ponytail: */ block comments (#810), so the ledger silently
|
|
44
|
+
// skipped every C/Java/CSS marker. Feed each shipped expression to real grep
|
|
45
|
+
// rather than restating it here: a copied regex only tests the copy.
|
|
46
|
+
const debtCopies = [
|
|
47
|
+
'skills/ponytail-debt/SKILL.md',
|
|
48
|
+
'.openclaw/skills/ponytail-debt/SKILL.md',
|
|
49
|
+
'commands/ponytail-debt.toml',
|
|
50
|
+
'.opencode/command/ponytail-debt.md',
|
|
51
|
+
];
|
|
52
|
+
const debtSamples = [
|
|
53
|
+
['# ponytail: hash marker', true],
|
|
54
|
+
['#ponytail: hash marker, no space', true],
|
|
55
|
+
['// ponytail: slash marker', true],
|
|
56
|
+
['//ponytail: slash marker, no space', true],
|
|
57
|
+
['/* ponytail: block marker, closed here */', true],
|
|
58
|
+
['/*ponytail: block marker, no space */', true],
|
|
59
|
+
['/* ponytail: block opener, closed some lines below', true],
|
|
60
|
+
['prose that merely mentions ponytail: markers stays out of the ledger', false],
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
test('every ponytail-debt copy greps #, // and /* markers but not prose', (t) => {
|
|
64
|
+
const input = debtSamples.map(([line]) => line).join('\n') + '\n';
|
|
65
|
+
const expected = debtSamples.flatMap(([line, hit], i) => (hit ? [`${i + 1}:${line}`] : []));
|
|
66
|
+
|
|
67
|
+
for (const rel of debtCopies) {
|
|
68
|
+
const documented = fs.readFileSync(path.join(root, rel), 'utf8').match(/git grep [^']*-nE '([^']+)'/);
|
|
69
|
+
assert.ok(documented, `${rel} no longer documents a git grep ... -nE '<pattern>'`);
|
|
70
|
+
|
|
71
|
+
const result = spawnSync('grep', ['-nE', documented[1]], { input, encoding: 'utf8', timeout: 5000 });
|
|
72
|
+
if (result.error) {
|
|
73
|
+
if (result.error.code === 'ENOENT') return t.skip('grep not available on this platform');
|
|
74
|
+
throw result.error;
|
|
75
|
+
}
|
|
76
|
+
assert.equal(result.status, 0, `grep failed for ${rel}: ${result.stderr}`);
|
|
77
|
+
assert.deepEqual(result.stdout.split('\n').filter(Boolean), expected, `${rel} misses or over-matches markers`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// The full documented command must skip .git and whatever .gitignore ignores, in any stack (#948, #1100).
|
|
82
|
+
test('the ponytail-debt scan skips .git and ignored dirs like node_modules, dist, build and .next', (t) => {
|
|
83
|
+
const cmd = fs.readFileSync(path.join(root, 'skills/ponytail-debt/SKILL.md'), 'utf8').match(/`(git grep [^`]+)`/)[1];
|
|
84
|
+
const dir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'ponytail-debt-'));
|
|
85
|
+
for (const sub of ['src', '.git', 'node_modules/pkg', 'dist', 'build', '.next']) {
|
|
86
|
+
fs.mkdirSync(path.join(dir, sub), { recursive: true });
|
|
87
|
+
fs.writeFileSync(path.join(dir, sub, 'a.js'), '// ponytail: marker\n');
|
|
88
|
+
}
|
|
89
|
+
fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\ndist/\nbuild/\n.next/\n');
|
|
90
|
+
const result = spawnSync('sh', ['-c', cmd], { cwd: dir, encoding: 'utf8', timeout: 5000 });
|
|
91
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
92
|
+
if (result.error || result.status === 127) return t.skip(`sh/git not available: ${result.error ? result.error.code : result.stderr}`);
|
|
93
|
+
assert.deepEqual(result.stdout.split('\n').filter(Boolean), ['src/a.js:1:// ponytail: marker']);
|
|
94
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Smoke test for the Copilot plugin adapter: keep command wiring minimal and
|
|
3
|
+
// ensure the debt command is part of the shared command surface.
|
|
4
|
+
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
const assert = require('node:assert/strict');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const REQUIRED_COMMAND_FILES = [
|
|
12
|
+
'ponytail.toml',
|
|
13
|
+
'ponytail-review.toml',
|
|
14
|
+
'ponytail-audit.toml',
|
|
15
|
+
'ponytail-debt.toml',
|
|
16
|
+
'ponytail-gain.toml',
|
|
17
|
+
'ponytail-help.toml',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function readJSON(relPath) {
|
|
21
|
+
return JSON.parse(fs.readFileSync(path.join(root, relPath), 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('copilot plugin command directory includes ponytail-debt', () => {
|
|
25
|
+
const manifest = readJSON('.github/plugin/plugin.json');
|
|
26
|
+
assert.equal(manifest.name, 'ponytail');
|
|
27
|
+
assert.equal(manifest.commands, 'commands/');
|
|
28
|
+
|
|
29
|
+
for (const file of REQUIRED_COMMAND_FILES) {
|
|
30
|
+
assert.ok(
|
|
31
|
+
fs.existsSync(path.join(root, manifest.commands, file)),
|
|
32
|
+
`missing command file: ${manifest.commands}${file}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Unit test for the correctness benchmark assertion. Feeds known-good and
|
|
3
|
+
// known-bad LLM outputs through each task checker and asserts the expected
|
|
4
|
+
// pass/fail verdict. Runs without promptfoo — just node:test + the module.
|
|
5
|
+
|
|
6
|
+
const test = require('node:test');
|
|
7
|
+
const assert = require('node:assert/strict');
|
|
8
|
+
const correctness = require('../benchmarks/correctness');
|
|
9
|
+
|
|
10
|
+
// Helper: wrap code in a fenced block and call the assertion with task vars.
|
|
11
|
+
function check(task, lang, code) {
|
|
12
|
+
const output = '```' + lang + '\n' + code + '\n```';
|
|
13
|
+
return correctness(output, { vars: { task } });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --- Email validator ---
|
|
17
|
+
|
|
18
|
+
test('email: correct one-liner passes', () => {
|
|
19
|
+
const result = check(
|
|
20
|
+
'Write me a Python function that validates email addresses.',
|
|
21
|
+
'python',
|
|
22
|
+
'def validate_email(email):\n return "@" in email and "." in email.split("@")[-1] and email.split("@")[0] != ""',
|
|
23
|
+
);
|
|
24
|
+
assert.equal(result.pass, true);
|
|
25
|
+
assert.equal(result.score, 1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('email: always-true validator fails', () => {
|
|
29
|
+
const result = check(
|
|
30
|
+
'Write me a Python function that validates email addresses.',
|
|
31
|
+
'python',
|
|
32
|
+
'def validate_email(email):\n return True',
|
|
33
|
+
);
|
|
34
|
+
assert.equal(result.pass, false);
|
|
35
|
+
assert.equal(result.score, 0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('email: no code block fails', () => {
|
|
39
|
+
const result = correctness('Here is my answer: just use regex.', {
|
|
40
|
+
vars: { task: 'Write me a Python function that validates email addresses.' },
|
|
41
|
+
});
|
|
42
|
+
assert.equal(result.pass, false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('email: bare unfenced code is still scored (#65)', () => {
|
|
46
|
+
const result = correctness('import re\ndef validate_email(e):\n return bool(re.match(r"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", e))', {
|
|
47
|
+
vars: { task: 'Write me a Python function that validates email addresses.' },
|
|
48
|
+
});
|
|
49
|
+
assert.equal(result.pass, true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// --- Debounce ---
|
|
53
|
+
|
|
54
|
+
test('debounce: correct implementation passes', () => {
|
|
55
|
+
const result = check(
|
|
56
|
+
'Add debounce to a search input in vanilla JavaScript.',
|
|
57
|
+
'javascript',
|
|
58
|
+
`function debounce(fn, delay) {
|
|
59
|
+
let timer;
|
|
60
|
+
return function(...args) {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
63
|
+
};
|
|
64
|
+
}`,
|
|
65
|
+
);
|
|
66
|
+
assert.equal(result.pass, true);
|
|
67
|
+
assert.equal(result.score, 1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('debounce: immediate-call implementation fails', () => {
|
|
71
|
+
const result = check(
|
|
72
|
+
'Add debounce to a search input in vanilla JavaScript.',
|
|
73
|
+
'javascript',
|
|
74
|
+
`function debounce(fn, delay) {
|
|
75
|
+
return function(...args) { fn.apply(this, args); };
|
|
76
|
+
}`,
|
|
77
|
+
);
|
|
78
|
+
assert.equal(result.pass, false);
|
|
79
|
+
assert.equal(result.score, 0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('debounce: bare unfenced arrow function is still scored (#65)', () => {
|
|
83
|
+
const result = correctness('const debounce = (fn, delay) => {\n let t;\n return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), delay); };\n};', {
|
|
84
|
+
vars: { task: 'Write a reusable debounce function in vanilla JavaScript: debounce(fn, delay).' },
|
|
85
|
+
});
|
|
86
|
+
assert.equal(result.pass, true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// --- CSV sum ---
|
|
90
|
+
|
|
91
|
+
test('csv: correct pandas one-liner passes', () => {
|
|
92
|
+
const result = check(
|
|
93
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
94
|
+
'python',
|
|
95
|
+
`import pandas as pd
|
|
96
|
+
df = pd.read_csv('sales.csv')
|
|
97
|
+
print(df['amount'].sum())`,
|
|
98
|
+
);
|
|
99
|
+
assert.equal(result.pass, true);
|
|
100
|
+
assert.equal(result.score, 1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('csv: an exception in the code is reported, not swallowed (#919)', () => {
|
|
104
|
+
const result = check(
|
|
105
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
106
|
+
'python',
|
|
107
|
+
`raise RuntimeError("boom")`,
|
|
108
|
+
);
|
|
109
|
+
assert.equal(result.pass, false);
|
|
110
|
+
assert.match(result.reason, /boom/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('csv: code that prints wrong value fails', () => {
|
|
114
|
+
const result = check(
|
|
115
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
116
|
+
'python',
|
|
117
|
+
`print(999)`,
|
|
118
|
+
);
|
|
119
|
+
assert.equal(result.pass, false);
|
|
120
|
+
assert.equal(result.score, 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('csv: value containing 351 as substring fails (e.g. 13510)', () => {
|
|
124
|
+
const result = check(
|
|
125
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
126
|
+
'python',
|
|
127
|
+
`print(13510)`,
|
|
128
|
+
);
|
|
129
|
+
assert.equal(result.pass, false);
|
|
130
|
+
assert.equal(result.score, 0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('csv: timeout can be raised for slow pandas startup', () => {
|
|
134
|
+
const previous = process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS;
|
|
135
|
+
try {
|
|
136
|
+
process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = '1';
|
|
137
|
+
const timedOut = check(
|
|
138
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
139
|
+
'python',
|
|
140
|
+
`import time
|
|
141
|
+
time.sleep(0.05)
|
|
142
|
+
print(351)`,
|
|
143
|
+
);
|
|
144
|
+
assert.equal(timedOut.pass, false);
|
|
145
|
+
assert.match(timedOut.reason, /ETIMEDOUT|timed out/i);
|
|
146
|
+
|
|
147
|
+
process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = '1000';
|
|
148
|
+
const completed = check(
|
|
149
|
+
"Write Python code that reads sales.csv and sums the 'amount' column.",
|
|
150
|
+
'python',
|
|
151
|
+
`import time
|
|
152
|
+
time.sleep(0.05)
|
|
153
|
+
print(351)`,
|
|
154
|
+
);
|
|
155
|
+
assert.equal(completed.pass, true);
|
|
156
|
+
assert.equal(completed.score, 1);
|
|
157
|
+
} finally {
|
|
158
|
+
if (previous === undefined) delete process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS;
|
|
159
|
+
else process.env.PONYTAIL_CORRECTNESS_TIMEOUT_MS = previous;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- React countdown ---
|
|
164
|
+
|
|
165
|
+
test('countdown: valid React component passes', () => {
|
|
166
|
+
const result = check(
|
|
167
|
+
'Build me a countdown timer component in React.',
|
|
168
|
+
'javascript',
|
|
169
|
+
`import { useState, useEffect } from 'react';
|
|
170
|
+
export default function Countdown({ seconds }) {
|
|
171
|
+
const [count, setCount] = useState(seconds);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (count <= 0) return;
|
|
174
|
+
const id = setInterval(() => setCount(prev => prev - 1), 1000);
|
|
175
|
+
return () => clearInterval(id);
|
|
176
|
+
}, [count]);
|
|
177
|
+
return <div>{count}</div>;
|
|
178
|
+
}`,
|
|
179
|
+
);
|
|
180
|
+
assert.equal(result.pass, true);
|
|
181
|
+
assert.equal(result.score, 1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('countdown: static div without state fails', () => {
|
|
185
|
+
const result = check(
|
|
186
|
+
'Build me a countdown timer component in React.',
|
|
187
|
+
'javascript',
|
|
188
|
+
`export default function Countdown() { return <div>10</div>; }`,
|
|
189
|
+
);
|
|
190
|
+
assert.equal(result.pass, false);
|
|
191
|
+
assert.equal(result.score, 0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// --- Rate limiter ---
|
|
195
|
+
|
|
196
|
+
test('ratelimit: FastAPI with limit logic passes', () => {
|
|
197
|
+
const result = check(
|
|
198
|
+
'Add rate limiting to my FastAPI endpoint so users can\'t spam it.',
|
|
199
|
+
'python',
|
|
200
|
+
`from fastapi import FastAPI, HTTPException
|
|
201
|
+
import time
|
|
202
|
+
|
|
203
|
+
app = FastAPI()
|
|
204
|
+
requests = {}
|
|
205
|
+
|
|
206
|
+
@app.get("/api")
|
|
207
|
+
def endpoint(user: str = "anon"):
|
|
208
|
+
now = time.time()
|
|
209
|
+
window = requests.get(user, [])
|
|
210
|
+
window = [t for t in window if now - t < 60]
|
|
211
|
+
if len(window) >= 10:
|
|
212
|
+
raise HTTPException(429, "Too Many Requests")
|
|
213
|
+
window.append(now)
|
|
214
|
+
requests[user] = window
|
|
215
|
+
return {"ok": True}`,
|
|
216
|
+
);
|
|
217
|
+
assert.equal(result.pass, true);
|
|
218
|
+
assert.equal(result.score, 1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('ratelimit: plain endpoint without limiting fails', () => {
|
|
222
|
+
const result = check(
|
|
223
|
+
'Add rate limiting to my FastAPI endpoint.',
|
|
224
|
+
'python',
|
|
225
|
+
`from fastapi import FastAPI
|
|
226
|
+
app = FastAPI()
|
|
227
|
+
|
|
228
|
+
@app.get("/api")
|
|
229
|
+
def endpoint():
|
|
230
|
+
return {"ok": True}`,
|
|
231
|
+
);
|
|
232
|
+
assert.equal(result.pass, false);
|
|
233
|
+
assert.equal(result.score, 0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// --- Edge cases ---
|
|
237
|
+
|
|
238
|
+
test('unknown task is gracefully skipped', () => {
|
|
239
|
+
const result = correctness('```python\nprint("hi")\n```', {
|
|
240
|
+
vars: { task: 'Explain quantum computing.' },
|
|
241
|
+
});
|
|
242
|
+
assert.equal(result.pass, true);
|
|
243
|
+
assert.equal(result.score, 1);
|
|
244
|
+
assert.match(result.reason, /unknown task/i);
|
|
245
|
+
});
|