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,39 @@
1
+ #!/usr/bin/env node
2
+ // Grok Build loads Ponytail through its native skill system. Lifecycle-hook
3
+ // stdout is passive in Grok, so this adapter must not register any hooks.
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
+
12
+ test('Grok manifest is a skill-only adapter with no lifecycle hooks', () => {
13
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, 'plugin.json'), 'utf8'));
14
+ assert.equal(manifest.name, 'ponytail');
15
+ assert.equal(manifest.hooks, undefined);
16
+ assert.equal(manifest.mcpServers, undefined);
17
+ assert.ok(!fs.existsSync(path.join(root, 'hooks', 'hooks.json')));
18
+ assert.ok(!fs.existsSync(path.join(root, '.grok-plugin', 'hooks.json')));
19
+ });
20
+
21
+ test('Ponytail skill describes every coding task for Grok auto-invocation', () => {
22
+ const skill = fs.readFileSync(path.join(root, 'skills', 'ponytail', 'SKILL.md'), 'utf8');
23
+ assert.match(skill, /Use on any\s+coding task/i);
24
+ assert.match(skill, /writing,\s+fixing, refactoring, reviewing/i);
25
+ assert.doesNotMatch(skill, /disable-model-invocation:\s*true/i);
26
+ });
27
+
28
+ test('Grok marketplace resolves a subdirectory with the same skills as direct installation', () => {
29
+ const catalog = JSON.parse(fs.readFileSync(path.join(root, '.grok-plugin', 'marketplace.json'), 'utf8'));
30
+ const entry = catalog.plugins.find((plugin) => plugin.name === 'ponytail');
31
+ const pluginRoot = path.resolve(root, entry.source);
32
+ // Grok rejects a marketplace source of "./", even though direct installation works.
33
+ assert.notEqual(pluginRoot, root, 'marketplace source must name a subdirectory');
34
+ const manifest = JSON.parse(fs.readFileSync(path.join(pluginRoot, 'plugin.json'), 'utf8'));
35
+ assert.equal(manifest.name, entry.name);
36
+ assert.equal(manifest.hooks, undefined);
37
+ assert.equal(manifest.mcpServers, undefined);
38
+ assert.equal(path.resolve(pluginRoot, manifest.skills), path.join(root, 'skills'));
39
+ });
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env node
2
+ // Hermes support is a real plugin, not just copied rules: the repo root must be
3
+ // installable with `hermes plugins install owner/repo`, register bundled skills,
4
+ // inject active mode context, and expose slash commands.
5
+
6
+ const test = require('node:test');
7
+ const assert = require('node:assert/strict');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { spawnSync } = require('child_process');
12
+ const { filterSkillBodyForMode } = require('../hooks/ponytail-instructions');
13
+
14
+ const commands = ['ponytail', 'ponytail-review', 'ponytail-audit', 'ponytail-debt', 'ponytail-gain', 'ponytail-help'];
15
+ const skillCommands = commands.filter((name) => name !== 'ponytail');
16
+
17
+ const root = path.join(__dirname, '..');
18
+
19
+ // ponytail: probe once; on Windows `python3` is the Store-alias stub that fails
20
+ // even when Python is installed, so fall back to `python` (mirrors benchmarks/correctness.js).
21
+ let pythonCmd;
22
+ function pythonExe() {
23
+ if (pythonCmd) return pythonCmd;
24
+ for (const cmd of ['python3', 'python']) {
25
+ if (spawnSync(cmd, ['-c', 'import sys'], { encoding: 'utf8' }).status === 0) {
26
+ return (pythonCmd = cmd);
27
+ }
28
+ }
29
+ return (pythonCmd = 'python3');
30
+ }
31
+
32
+ function python(script, env = {}) {
33
+ const result = spawnSync(pythonExe(), ['-c', script], {
34
+ cwd: root,
35
+ // Isolate from the developer's own ~/.config/ponytail and PONYTAIL_DEFAULT_MODE.
36
+ env: {
37
+ ...process.env,
38
+ PONYTAIL_DEFAULT_MODE: '',
39
+ XDG_CONFIG_HOME: fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-config-')),
40
+ ...env,
41
+ },
42
+ encoding: 'utf8',
43
+ });
44
+ if (result.status !== 0) {
45
+ throw new Error(`python failed\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`);
46
+ }
47
+ return result.stdout.trim();
48
+ }
49
+
50
+ test('Hermes plugin manifest matches runtime skills, hooks, commands, and package version', () => {
51
+ const manifestPath = path.join(root, 'plugin.yaml');
52
+ assert.ok(fs.existsSync(manifestPath), 'missing root plugin.yaml');
53
+ const manifest = fs.readFileSync(manifestPath, 'utf8');
54
+ const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
55
+ const skillDirs = fs.readdirSync(path.join(root, 'skills'))
56
+ .filter((name) => fs.existsSync(path.join(root, 'skills', name, 'SKILL.md')))
57
+ .sort();
58
+
59
+ assert.match(manifest, /^name:\s*ponytail$/m);
60
+ assert.match(manifest, new RegExp(`^version:\\s*${packageJson.version}$`, 'm'));
61
+ assert.match(manifest, new RegExp(`^author:\\s*${packageJson.author.name}$`, 'm'));
62
+ assert.deepEqual(commands.filter((name) => manifest.includes(` - ${name}`)), commands);
63
+ assert.deepEqual(skillDirs.filter((name) => manifest.includes(` - ${name}`)), skillDirs);
64
+ assert.match(manifest, /pre_llm_call/);
65
+ assert.match(manifest, /pre_gateway_dispatch/);
66
+ });
67
+
68
+ test('Hermes plugin registers every shipped skill under the ponytail namespace', () => {
69
+ const output = python(String.raw`
70
+ import importlib.util, json, pathlib
71
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
72
+ mod = importlib.util.module_from_spec(spec)
73
+ spec.loader.exec_module(mod)
74
+ class Ctx:
75
+ def __init__(self):
76
+ self.skills = []
77
+ self.hooks = []
78
+ self.commands = []
79
+ def register_skill(self, name, path):
80
+ self.skills.append((name, pathlib.Path(path).as_posix()))
81
+ def register_hook(self, name, handler):
82
+ self.hooks.append(name)
83
+ def register_command(self, name, handler, description='', args_hint=''):
84
+ self.commands.append(name)
85
+ ctx = Ctx()
86
+ mod.register(ctx)
87
+ print(json.dumps({'skills': ctx.skills, 'hooks': ctx.hooks, 'commands': ctx.commands}, sort_keys=True))
88
+ `);
89
+ const data = JSON.parse(output);
90
+ assert.deepEqual(data.skills.map(([name]) => name).sort(), [
91
+ 'ponytail',
92
+ 'ponytail-audit',
93
+ 'ponytail-debt',
94
+ 'ponytail-gain',
95
+ 'ponytail-help',
96
+ 'ponytail-review',
97
+ ]);
98
+ assert.ok(data.skills.every(([, skillPath]) => skillPath.endsWith('/SKILL.md')));
99
+ assert.ok(data.hooks.includes('pre_llm_call'));
100
+ assert.ok(data.commands.includes('ponytail'));
101
+ assert.ok(data.commands.includes('ponytail-review'));
102
+ });
103
+
104
+ test('Hermes plugin builds mode-aware injected context from the canonical skill', () => {
105
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-config-'));
106
+ const output = python(String.raw`
107
+ import importlib.util, json
108
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
109
+ mod = importlib.util.module_from_spec(spec)
110
+ spec.loader.exec_module(mod)
111
+ ctx = mod.build_injected_context('ultra')
112
+ print(json.dumps({'ctx': ctx}))
113
+ `, { XDG_CONFIG_HOME: tmp });
114
+ const { ctx } = JSON.parse(output);
115
+
116
+ assert.match(ctx, /PONYTAIL MODE ACTIVE — level: ultra/);
117
+ assert.match(ctx, /The best\s+code is the code never written/);
118
+ assert.match(ctx, /ultra/i);
119
+ assert.doesNotMatch(ctx, /^---/);
120
+ assert.doesNotMatch(ctx, /\|\s*\*\*Lite\*\*/i);
121
+ });
122
+
123
+ test('Hermes mode config respects env, config file, off, and invalid command behavior', () => {
124
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-config-'));
125
+ fs.mkdirSync(path.join(tmp, 'ponytail'), { recursive: true });
126
+ fs.writeFileSync(path.join(tmp, 'ponytail', 'config.json'), JSON.stringify({ defaultMode: 'lite' }));
127
+ const output = python(String.raw`
128
+ import importlib.util, json
129
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
130
+ mod = importlib.util.module_from_spec(spec)
131
+ spec.loader.exec_module(mod)
132
+ class Ctx:
133
+ def __init__(self): self.commands = {}
134
+ def register_skill(self, name, path): pass
135
+ def register_hook(self, name, handler): pass
136
+ def register_command(self, name, handler, description='', args_hint=''):
137
+ self.commands[name] = handler
138
+ ctx = Ctx()
139
+ mod.register(ctx)
140
+ status_before = ctx.commands['ponytail']('')
141
+ invalid = ctx.commands['ponytail']('maximum')
142
+ status_after = ctx.commands['ponytail']('')
143
+ print(json.dumps({
144
+ 'default': mod.build_injected_context(None),
145
+ 'off': mod.build_injected_context('off'),
146
+ 'status_before': status_before,
147
+ 'invalid': invalid,
148
+ 'status_after': status_after,
149
+ }))
150
+ `, { XDG_CONFIG_HOME: tmp, PONYTAIL_DEFAULT_MODE: 'ultra' });
151
+ const data = JSON.parse(output);
152
+ assert.match(data.default, /level: ultra/);
153
+ assert.equal(data.off, '');
154
+ assert.match(data.status_before, /Ponytail mode: ultra/);
155
+ assert.match(data.invalid, /Usage:/);
156
+ assert.match(data.status_after, /Ponytail mode: ultra/);
157
+ });
158
+
159
+ test('Hermes honors plain and BOM-prefixed default-mode config before LLM calls', (t) => {
160
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-config-'));
161
+ t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
162
+ fs.mkdirSync(path.join(tmp, 'ponytail'));
163
+ for (const prefix of ['', '\uFEFF']) {
164
+ for (const mode of ['lite', 'off']) {
165
+ fs.writeFileSync(path.join(tmp, 'ponytail', 'config.json'), prefix + JSON.stringify({ defaultMode: mode }));
166
+ const output = python(String.raw`
167
+ import importlib.util, json
168
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
169
+ mod = importlib.util.module_from_spec(spec)
170
+ spec.loader.exec_module(mod)
171
+ print(json.dumps(mod._pre_llm_call()))
172
+ `, { XDG_CONFIG_HOME: tmp, PONYTAIL_DEFAULT_MODE: '' });
173
+ const data = JSON.parse(output);
174
+ if (mode === 'off') assert.equal(data, null);
175
+ else assert.match(data.context, /PONYTAIL MODE ACTIVE — level: lite/);
176
+ }
177
+ }
178
+ });
179
+
180
+ test('Hermes treats review as a command, never a level', () => {
181
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-review-default-'));
182
+ fs.mkdirSync(path.join(tmp, 'ponytail'), { recursive: true });
183
+ fs.writeFileSync(path.join(tmp, 'ponytail', 'config.json'), JSON.stringify({ defaultMode: 'review' }));
184
+ const output = python(String.raw`
185
+ import importlib.util, json, os
186
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
187
+ mod = importlib.util.module_from_spec(spec)
188
+ spec.loader.exec_module(mod)
189
+ env_default = mod.build_injected_context(None)
190
+ del os.environ['PONYTAIL_DEFAULT_MODE']
191
+ file_default = mod.build_injected_context(None)
192
+ explicit_review = mod.build_injected_context('review')
193
+ print(json.dumps({
194
+ 'env_default': env_default,
195
+ 'file_default': file_default,
196
+ 'explicit_review': explicit_review,
197
+ }))
198
+ `, { XDG_CONFIG_HOME: tmp, PONYTAIL_DEFAULT_MODE: 'review' });
199
+ const data = JSON.parse(output);
200
+ assert.match(data.env_default, /PONYTAIL MODE ACTIVE — level: full/);
201
+ assert.match(data.file_default, /PONYTAIL MODE ACTIVE — level: full/);
202
+ assert.match(data.explicit_review, /PONYTAIL MODE ACTIVE — level: full/);
203
+ });
204
+
205
+ test('Hermes /ponytail command changes mode and pre_llm_call injects current context', () => {
206
+ const output = python(String.raw`
207
+ import importlib.util, json
208
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
209
+ mod = importlib.util.module_from_spec(spec)
210
+ spec.loader.exec_module(mod)
211
+ class Ctx:
212
+ def __init__(self):
213
+ self.hooks = {}
214
+ self.commands = {}
215
+ def register_skill(self, name, path): pass
216
+ def register_hook(self, name, handler): self.hooks[name] = handler
217
+ def register_command(self, name, handler, description='', args_hint=''):
218
+ self.commands[name] = handler
219
+ ctx = Ctx()
220
+ mod.register(ctx)
221
+ message = ctx.commands['ponytail']('ultra')
222
+ injected = ctx.hooks['pre_llm_call'](session_id='s1', user_message='build it', conversation_history=[], is_first_turn=False, model='m', platform='cli')
223
+ print(json.dumps({'message': message, 'context': injected['context']}))
224
+ `);
225
+ const data = JSON.parse(output);
226
+ assert.match(data.message, /ultra/);
227
+ assert.match(data.context, /PONYTAIL MODE ACTIVE — level: ultra/);
228
+ });
229
+
230
+ test('Hermes pre_llm_call does not append the same ruleset to every persisted turn', () => {
231
+ const output = python(String.raw`
232
+ import importlib.util, json
233
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
234
+ mod = importlib.util.module_from_spec(spec)
235
+ spec.loader.exec_module(mod)
236
+ first = mod._pre_llm_call(conversation_history=[])
237
+ history = [{
238
+ 'role': 'user',
239
+ 'content': 'first task',
240
+ 'api_content': 'first task\\n\\n' + first['context'],
241
+ }]
242
+ second = mod._pre_llm_call(conversation_history=history)
243
+ mod._current_mode = 'ultra'
244
+ switched = mod._pre_llm_call(conversation_history=history)
245
+ history.append({
246
+ 'role': 'user',
247
+ 'content': 'second task',
248
+ 'api_content': 'second task\\n\\n' + switched['context'],
249
+ })
250
+ after_switch = mod._pre_llm_call(conversation_history=history)
251
+ print(json.dumps({'first': first, 'second': second, 'switched': switched, 'after_switch': after_switch}))
252
+ `);
253
+ const data = JSON.parse(output);
254
+ assert.match(data.first.context, /PONYTAIL MODE ACTIVE — level: full/);
255
+ assert.equal(data.second, null);
256
+ assert.match(data.switched.context, /PONYTAIL MODE ACTIVE — level: ultra/);
257
+ assert.equal(data.after_switch, null);
258
+ });
259
+
260
+ test('Hermes /ponytail off reaches the model once, and a later level re-injects the ruleset', () => {
261
+ // The ruleset from the first turn is replayed from api_content on every later
262
+ // turn, and the command's reply goes to the user only, so off must be injected.
263
+ const output = python(String.raw`
264
+ import importlib.util, json
265
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
266
+ mod = importlib.util.module_from_spec(spec)
267
+ spec.loader.exec_module(mod)
268
+ first = mod._pre_llm_call(conversation_history=[])
269
+ history = [{
270
+ 'role': 'user',
271
+ 'content': 'first task',
272
+ 'api_content': 'first task\\n\\n' + first['context'],
273
+ }]
274
+ mod._handle_mode_command('off')
275
+ nothing_persisted = mod._pre_llm_call(conversation_history=[])
276
+ off = mod._pre_llm_call(conversation_history=history)
277
+ history.append({
278
+ 'role': 'user',
279
+ 'content': 'second task',
280
+ 'api_content': 'second task\\n\\nPONYTAIL MODE OFF',
281
+ })
282
+ still_off = mod._pre_llm_call(conversation_history=history)
283
+ mod._handle_mode_command('full')
284
+ back_on = mod._pre_llm_call(conversation_history=history)
285
+ print(json.dumps({'nothing_persisted': nothing_persisted, 'off': off, 'still_off': still_off, 'back_on': back_on}))
286
+ `);
287
+ const data = JSON.parse(output);
288
+ assert.equal(data.nothing_persisted, null);
289
+ assert.deepEqual(data.off, { context: 'PONYTAIL MODE OFF' });
290
+ assert.equal(data.still_off, null);
291
+ assert.match(data.back_on.context, /PONYTAIL MODE ACTIVE — level: full/);
292
+ });
293
+
294
+ test('Hermes gateway rewrite respects slash access denial', () => {
295
+ const output = python(String.raw`
296
+ import importlib.util, json
297
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
298
+ mod = importlib.util.module_from_spec(spec)
299
+ spec.loader.exec_module(mod)
300
+ class Source:
301
+ platform = None
302
+ chat_id = 'c1'
303
+ user_id = 'u1'
304
+ class Event:
305
+ text = '/ponytail-review src/app.js'
306
+ source = Source()
307
+ class Gateway:
308
+ def _check_slash_access(self, source, command):
309
+ return 'denied'
310
+ result = mod.rewrite_gateway_command(event=Event(), gateway=Gateway())
311
+ print(json.dumps(result))
312
+ `);
313
+ assert.equal(output, 'null');
314
+ });
315
+
316
+ test('Hermes bare /ponytail switches an off session on and only reports a running level (#639)', () => {
317
+ const output = python(String.raw`
318
+ import importlib.util, json
319
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
320
+ mod = importlib.util.module_from_spec(spec)
321
+ spec.loader.exec_module(mod)
322
+ mod._current_mode = 'ultra'
323
+ running = mod._handle_mode_command('')
324
+ kept = mod._current_mode
325
+ mod._current_mode = 'off'
326
+ switched = mod._handle_mode_command('')
327
+ print(json.dumps([running, kept, switched, mod._current_mode]))
328
+ `);
329
+ const [running, kept, switched, after] = JSON.parse(output);
330
+ assert.match(running, /Ponytail mode: ultra/);
331
+ assert.equal(kept, 'ultra');
332
+ assert.match(switched, /set to full/);
333
+ assert.equal(after, 'full');
334
+ });
335
+
336
+ test('Hermes gateway rewrite preserves every skill command and ignores unrelated text', () => {
337
+ const output = python(String.raw`
338
+ import importlib.util, json
339
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
340
+ mod = importlib.util.module_from_spec(spec)
341
+ spec.loader.exec_module(mod)
342
+ class Event:
343
+ def __init__(self, text): self.text = text
344
+ cases = {}
345
+ for text in ['/ponytail-review x', '/ponytail_audit repo', '/ponytail-debt', '/ponytail-help', '/status', 'hello']:
346
+ cases[text] = mod.rewrite_gateway_command(event=Event(text))
347
+ print(json.dumps(cases, sort_keys=True))
348
+ `);
349
+ const data = JSON.parse(output);
350
+ assert.match(data['/ponytail-review x'].text, /ponytail-review/);
351
+ assert.match(data['/ponytail_audit repo'].text, /ponytail-audit/);
352
+ assert.match(data['/ponytail_audit repo'].text, /repo/);
353
+ assert.match(data['/ponytail-debt'].text, /ponytail-debt/);
354
+ assert.match(data['/ponytail-help'].text, /ponytail-help/);
355
+ assert.equal(data['/status'], null);
356
+ assert.equal(data.hello, null);
357
+ });
358
+
359
+ test('Hermes mode filter does not drop a rule bullet whose label matches a mode name', () => {
360
+ // Regression for the Hermes side of #571: a rule bullet shaped "- Full: ..."
361
+ // has the same "label: text" shape as a worked example, but isn't one — it
362
+ // must survive in every mode. Only the quoted `- lite: "..."`-style bullets
363
+ // are real per-mode examples. hooks/ponytail-instructions.js already guards
364
+ // against this; the Hermes plugin filter had drifted and lost the same guard.
365
+ const output = python(String.raw`
366
+ import importlib.util, json
367
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
368
+ mod = importlib.util.module_from_spec(spec)
369
+ spec.loader.exec_module(mod)
370
+ body = (
371
+ "- Full: do not confuse this rule label with the mode name.\n"
372
+ "- Lite: same risk, this is a real rule bullet.\n"
373
+ '- lite: "real worked example"\n'
374
+ '- ultra: "real worked example"'
375
+ )
376
+ print(json.dumps({'filtered': mod._filter_skill_body_for_mode(body, 'ultra')}))
377
+ `);
378
+ const { filtered } = JSON.parse(output);
379
+ assert.ok(filtered.includes('Full: do not confuse'), 'an unquoted rule bullet must not be treated as a mode example');
380
+ assert.ok(filtered.includes('Lite: same risk'), 'an unquoted rule bullet must not be treated as a mode example');
381
+ assert.ok(!filtered.includes('- lite:'), 'the real quoted lite example must still be filtered out in ultra mode');
382
+ assert.ok(filtered.includes('ultra: "real worked example"'));
383
+ });
384
+
385
+ test('Hermes mode filter stays in lockstep with the JS filter on the real skill body', () => {
386
+ // The two filters are independent ports of the same contract (one shared
387
+ // skill file, every host adapter trims it the same way). Diff them directly
388
+ // on skills/ponytail/SKILL.md instead of trusting the two implementations to
389
+ // stay in sync by hand.
390
+ const body = fs.readFileSync(path.join(root, 'skills', 'ponytail', 'SKILL.md'), 'utf8');
391
+ for (const mode of ['lite', 'full', 'ultra']) {
392
+ const jsFiltered = filterSkillBodyForMode(body, mode);
393
+ const pyFiltered = python(String.raw`
394
+ import importlib.util, json
395
+ spec = importlib.util.spec_from_file_location('ponytail_hermes_plugin', '__init__.py')
396
+ mod = importlib.util.module_from_spec(spec)
397
+ spec.loader.exec_module(mod)
398
+ body = open('skills/ponytail/SKILL.md', encoding='utf-8').read()
399
+ print(json.dumps(mod._filter_skill_body_for_mode(body, '${mode}')))
400
+ `);
401
+ assert.equal(JSON.parse(pyFiltered), jsFiltered, `filters diverge for mode "${mode}"`);
402
+ }
403
+ });