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.
Files changed (240) hide show
  1. package/.agents/rules/ponytail.md +30 -0
  2. package/.claude-plugin/contrast-mcp-launcher.mjs +31 -0
  3. package/.claude-plugin/marketplace.json +17 -0
  4. package/.claude-plugin/plugin.json +8 -3
  5. package/.cline-plugin/plugin.js +6 -0
  6. package/.clinerules/ponytail.md +30 -0
  7. package/.codex-plugin/plugin.json +4 -3
  8. package/.cursor/rules/ponytail.mdc +36 -0
  9. package/.cursor-plugin/marketplace.json +16 -0
  10. package/.cursor-plugin/plugin.json +2 -2
  11. package/.devin-plugin/plugin.json +2 -2
  12. package/.grok-plugin/marketplace.json +16 -0
  13. package/.kimi-plugin/plugin.json +11 -29
  14. package/.kiro/steering/ponytail.md +35 -0
  15. package/.omp-plugin/marketplace.json +15 -0
  16. package/.openclaw/skills/ponytail/SKILL.md +47 -0
  17. package/.openclaw/skills/ponytail-audit/SKILL.md +100 -0
  18. package/.openclaw/skills/ponytail-debt/SKILL.md +43 -0
  19. package/.openclaw/skills/ponytail-gain/SKILL.md +51 -0
  20. package/.openclaw/skills/ponytail-help/SKILL.md +76 -0
  21. package/.openclaw/skills/ponytail-review/SKILL.md +98 -0
  22. package/.opencode/command/ponytail-audit.md +5 -0
  23. package/.opencode/command/ponytail-debt.md +5 -0
  24. package/.opencode/command/ponytail-gain.md +5 -0
  25. package/.opencode/command/ponytail-help.md +5 -0
  26. package/.opencode/command/ponytail-review.md +5 -0
  27. package/.opencode/command/ponytail.md +5 -0
  28. package/.opencode/plugins/index.js +4 -0
  29. package/.opencode/plugins/ponytail-frontmatter.cjs +63 -0
  30. package/.opencode/plugins/ponytail.mjs +206 -0
  31. package/.qoder-plugin/plugin.json +12 -26
  32. package/.windsurf/rules/ponytail.md +30 -0
  33. package/AGENTS.md +32 -0
  34. package/__init__.py +228 -0
  35. package/assets/antislop-banner.png +0 -0
  36. package/assets/band/band.png +0 -0
  37. package/assets/band/board.png +0 -0
  38. package/assets/benchmark-3model.svg +21 -0
  39. package/assets/benchmark-agentic.svg +62 -0
  40. package/assets/codex-icon.png +0 -0
  41. package/assets/compare/code/after.webp +0 -0
  42. package/assets/compare/code/before.webp +0 -0
  43. package/assets/compare/text/after.webp +0 -0
  44. package/assets/compare/text/before.webp +0 -0
  45. package/assets/compare/ui/w-all.webp +0 -0
  46. package/assets/compare/ui/w-antislop.webp +0 -0
  47. package/assets/compare/ui/w-design.webp +0 -0
  48. package/assets/compare/ui/without.webp +0 -0
  49. package/assets/logo-dark.png +0 -0
  50. package/assets/logo-dark.svg +115 -0
  51. package/assets/logo-greenpt-dark.svg +27 -0
  52. package/assets/logo-greenpt.svg +27 -0
  53. package/assets/logo.png +0 -0
  54. package/assets/photoreunion-logo-dark.svg +4 -0
  55. package/assets/photoreunion-logo-light.svg +4 -0
  56. package/assets/profile.png +0 -0
  57. package/assets/retriever-logo-dark.svg +4 -0
  58. package/assets/retriever-logo-light.svg +4 -0
  59. package/assets/stories-banner.png +0 -0
  60. package/assets/v5/audit-es.png +0 -0
  61. package/assets/v5/audit-ja.png +0 -0
  62. package/assets/v5/audit-ko.png +0 -0
  63. package/assets/v5/audit-zh-CN.png +0 -0
  64. package/assets/v5/audit.png +0 -0
  65. package/assets/v5/beforeafter-es.png +0 -0
  66. package/assets/v5/beforeafter-ja.png +0 -0
  67. package/assets/v5/beforeafter-ko.png +0 -0
  68. package/assets/v5/beforeafter-zh-CN.png +0 -0
  69. package/assets/v5/beforeafter.png +0 -0
  70. package/assets/v5/chart-es.png +0 -0
  71. package/assets/v5/chart-ja.png +0 -0
  72. package/assets/v5/chart-ko.png +0 -0
  73. package/assets/v5/chart-zh-CN.png +0 -0
  74. package/assets/v5/chart.png +0 -0
  75. package/assets/v5/hero-es.jpg +0 -0
  76. package/assets/v5/hero-ja.jpg +0 -0
  77. package/assets/v5/hero-ko.jpg +0 -0
  78. package/assets/v5/hero-zh-CN.jpg +0 -0
  79. package/assets/v5/hero.jpg +0 -0
  80. package/assets/v5/ladder-es.png +0 -0
  81. package/assets/v5/ladder-ja.png +0 -0
  82. package/assets/v5/ladder-ko.png +0 -0
  83. package/assets/v5/ladder-zh-CN.png +0 -0
  84. package/assets/v5/ladder.png +0 -0
  85. package/assets/v5/review-es.png +0 -0
  86. package/assets/v5/review-ja.png +0 -0
  87. package/assets/v5/review-ko.png +0 -0
  88. package/assets/v5/review-zh-CN.png +0 -0
  89. package/assets/v5/review.png +0 -0
  90. package/assets/v5/tests-es.png +0 -0
  91. package/assets/v5/tests-ja.png +0 -0
  92. package/assets/v5/tests-ko.png +0 -0
  93. package/assets/v5/tests-zh-CN.png +0 -0
  94. package/assets/v5/tests.png +0 -0
  95. package/assets/waitlist-banner-es.png +0 -0
  96. package/assets/waitlist-banner-ja.png +0 -0
  97. package/assets/waitlist-banner-ko.png +0 -0
  98. package/assets/waitlist-banner-zh-CN.png +0 -0
  99. package/assets/waitlist-banner.png +0 -0
  100. package/benchmarks/.env.example +2 -0
  101. package/benchmarks/agentic/README.md +201 -0
  102. package/benchmarks/agentic/answer.py +288 -0
  103. package/benchmarks/agentic/compare.py +93 -0
  104. package/benchmarks/agentic/complete.py +178 -0
  105. package/benchmarks/agentic/judge.py +198 -0
  106. package/benchmarks/agentic/run.py +543 -0
  107. package/benchmarks/agentic/tasks.py +1544 -0
  108. package/benchmarks/arms/baseline.js +2 -0
  109. package/benchmarks/arms/caveman-SKILL.md +67 -0
  110. package/benchmarks/arms/caveman.js +8 -0
  111. package/benchmarks/arms/ponytail.js +8 -0
  112. package/benchmarks/behavior.js +54 -0
  113. package/benchmarks/behavior.yaml +40 -0
  114. package/benchmarks/correctness.js +291 -0
  115. package/benchmarks/loc.js +17 -19
  116. package/benchmarks/promptfooconfig.gemini.yaml +32 -0
  117. package/benchmarks/promptfooconfig.gpt-newest.yaml +33 -0
  118. package/benchmarks/promptfooconfig.yaml +41 -0
  119. package/benchmarks/results/2026-06-12-caveman-vs-ponytail.md +71 -0
  120. package/benchmarks/results/2026-06-12-v4-hardening-vs-caveman.md +125 -0
  121. package/benchmarks/results/2026-06-15-llama3.2-local.md +82 -0
  122. package/benchmarks/results/2026-06-16-correctness-gate-fix.md +113 -0
  123. package/benchmarks/results/2026-06-16-robustness-audit.md +135 -0
  124. package/benchmarks/results/2026-06-17-agentic-safety.md +164 -0
  125. package/benchmarks/results/2026-06-17-cost-verification.md +93 -0
  126. package/benchmarks/results/2026-06-18-agentic.md +219 -0
  127. package/benchmarks/results/2026-06-22-issue-245-217-comprehension.md +98 -0
  128. package/benchmarks/results/2026-10-07-agentic.md +186 -0
  129. package/cli/index.mjs +241 -0
  130. package/cli/lib/banner.mjs +21 -0
  131. package/cli/lib/install.mjs +498 -0
  132. package/cli/lib/settings.mjs +48 -0
  133. package/cli/package-lock.json +87 -0
  134. package/cli/package.json +42 -0
  135. package/cli/scripts/smoke-test.mjs +95 -0
  136. package/cli/scripts/smoke-worker.mjs +411 -0
  137. package/cli/scripts/sync-skills.mjs +81 -0
  138. package/commands/ponytail-audit.toml +2 -0
  139. package/commands/ponytail-debt.toml +2 -0
  140. package/commands/ponytail-gain.toml +2 -0
  141. package/commands/ponytail-help.toml +2 -0
  142. package/commands/ponytail-review.toml +2 -0
  143. package/commands/ponytail.toml +2 -0
  144. package/examples/email-validation.md +156 -0
  145. package/examples/group-by.md +35 -0
  146. package/examples/infinite-scroll.md +58 -0
  147. package/examples/number-formatting.md +37 -0
  148. package/examples/rate-limit.md +272 -0
  149. package/examples/react-countdown.md +390 -0
  150. package/examples/url-params.md +41 -0
  151. package/gemini-extension.json +2 -2
  152. package/hooks/band/board.ts +560 -0
  153. package/hooks/band/logo.ts +3 -0
  154. package/hooks/band/metrics.ts +311 -0
  155. package/hooks/band/register.tsx +495 -0
  156. package/hooks/band/types.d.ts +34 -0
  157. package/hooks/chill-statusline.ps1 +1 -1
  158. package/hooks/claude-band.json +6 -0
  159. package/hooks/claude-codex-hooks.json +34 -4
  160. package/hooks/copilot-hooks.json +12 -2
  161. package/hooks/cursor-hooks.json +2 -2
  162. package/hooks/ponytail-activate.js +201 -0
  163. package/hooks/ponytail-band-import.js +274 -0
  164. package/hooks/ponytail-config.js +175 -0
  165. package/hooks/ponytail-instructions.js +98 -0
  166. package/hooks/ponytail-map.js +111 -0
  167. package/hooks/ponytail-mode-tracker.js +216 -0
  168. package/hooks/ponytail-runtime.js +182 -0
  169. package/hooks/ponytail-statusline.ps1 +24 -0
  170. package/hooks/ponytail-statusline.sh +18 -0
  171. package/hooks/ponytail-subagent.js +94 -0
  172. package/hooks/qoder-hooks.json +19 -4
  173. package/i18n/README.ko.md +230 -0
  174. package/package.json +17 -6
  175. package/pi-extension/index.js +254 -0
  176. package/pi-extension/package.json +8 -0
  177. package/pi-extension/test/extension.test.js +367 -0
  178. package/pi-extension/test/helpers.test.js +179 -0
  179. package/plugin.yaml +21 -12
  180. package/rules/antislop.mdc +18 -0
  181. package/scripts/build-openclaw-skills.js +60 -0
  182. package/scripts/check-repo.mjs +389 -0
  183. package/scripts/check-rule-copies.js +74 -0
  184. package/scripts/check-versions.js +77 -0
  185. package/scripts/cursor-hooks.js +145 -0
  186. package/scripts/generate-chillslop.mjs +42 -0
  187. package/scripts/generate-examples.mjs +149 -0
  188. package/scripts/generate-plugin-doors.mjs +108 -0
  189. package/scripts/publish-openclaw-skills.js +75 -0
  190. package/scripts/skill-index.mjs +81 -0
  191. package/scripts/sync-ponytail.mjs +65 -0
  192. package/scripts/uninstall.js +92 -0
  193. package/skills/antislop/SKILL.md +700 -0
  194. package/skills/antislop/VERSION +1 -0
  195. package/skills/antislop-code/SKILL.md +128 -0
  196. package/skills/antislop-copywriting/SKILL.md +407 -0
  197. package/skills/antislop-human/LICENSE-web-quality-skills +21 -0
  198. package/skills/antislop-human/SKILL.md +204 -0
  199. package/skills/antislop-human/contrast-check.py +168 -0
  200. package/skills/antislop-human/contrast-mcp.py +121 -0
  201. package/skills/antislop-human/references/A11Y-PATTERNS.md +249 -0
  202. package/skills/antislop-human/references/WCAG.md +127 -0
  203. package/skills/antislop-layoutmobile/SKILL.md +170 -0
  204. package/skills/antislop-ui/LICENSE-web-quality-skills +21 -0
  205. package/skills/antislop-ui/SKILL.md +344 -0
  206. package/skills/antislop-ui/references/CORE-WEB-VITALS.md +181 -0
  207. package/skills/antislop-ui/references/WEB-BASELINE.md +28 -0
  208. package/skills/plugin.json +5 -0
  209. package/skills/ponytail/SKILL.md +52 -0
  210. package/skills/ponytail-audit/SKILL.md +103 -0
  211. package/skills/ponytail-debt/SKILL.md +44 -0
  212. package/skills/ponytail-gain/SKILL.md +52 -0
  213. package/skills/ponytail-help/SKILL.md +76 -0
  214. package/skills/ponytail-review/SKILL.md +101 -0
  215. package/src/package.json +3 -0
  216. package/tests/activation-map.test.js +63 -0
  217. package/tests/band-import.test.js +111 -0
  218. package/tests/band.test.tsx +520 -0
  219. package/tests/behavior.test.js +80 -0
  220. package/tests/commands.test.js +94 -0
  221. package/tests/copilot-plugin.test.js +35 -0
  222. package/tests/correctness.test.js +245 -0
  223. package/tests/cursor-hooks.test.js +343 -0
  224. package/tests/gemini-extension.test.js +91 -0
  225. package/tests/grok-plugin.test.js +39 -0
  226. package/tests/hermes-plugin.test.js +403 -0
  227. package/tests/hooks-windows.test.js +205 -0
  228. package/tests/hooks.test.js +1005 -0
  229. package/tests/kimi-plugin.test.js +25 -0
  230. package/tests/loc.test.js +18 -0
  231. package/tests/map.test.js +30 -0
  232. package/tests/openclaw-skills.test.js +26 -0
  233. package/tests/opencode-plugin.test.js +340 -0
  234. package/tests/package-scripts.test.js +35 -0
  235. package/tests/package.test.js +23 -0
  236. package/tests/qoder-plugin.test.js +84 -0
  237. package/tests/uninstall.test.js +202 -0
  238. /package/bin/{chill-agent.js → chill-agent.mjs} +0 -0
  239. /package/tests/{manifests.test.js → manifests.test.mjs} +0 -0
  240. /package/tests/{rules.test.js → rules.test.mjs} +0 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+
3
+ const assert = require('assert');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const root = path.join(__dirname, '..');
10
+
11
+ function runUninstall(env) {
12
+ return spawnSync(process.execPath, [path.join(root, 'scripts', 'uninstall.js')], {
13
+ env: { ...process.env, ...env },
14
+ encoding: 'utf8',
15
+ });
16
+ }
17
+
18
+ delete process.env.CLAUDE_CONFIG_DIR;
19
+
20
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-uninstall-'));
21
+ process.on('exit', () => fs.rmSync(temp, { recursive: true, force: true }));
22
+
23
+ const home = path.join(temp, 'home');
24
+ const claudeDir = path.join(home, '.claude');
25
+ fs.mkdirSync(claudeDir, { recursive: true });
26
+
27
+ const flagPath = path.join(claudeDir, '.ponytail-active');
28
+ fs.writeFileSync(flagPath, 'full');
29
+
30
+ // Qoder keeps its flag in ~/.qoder (hooks/ponytail-runtime.js), and since
31
+ // #676 it holds "off", so a leftover would start a reinstall switched off.
32
+ const qoderFlagPath = path.join(home, '.qoder', '.ponytail-active');
33
+ fs.mkdirSync(path.dirname(qoderFlagPath), { recursive: true });
34
+ fs.writeFileSync(qoderFlagPath, 'ultra');
35
+ const qoderCustomDir = path.join(temp, 'qoder-custom-config');
36
+ const qoderCustomFlagPath = path.join(qoderCustomDir, '.ponytail-active');
37
+ fs.mkdirSync(qoderCustomDir, { recursive: true });
38
+ fs.writeFileSync(qoderCustomFlagPath, 'lite');
39
+
40
+ // OpenCode keeps its flag beside its own config; a leftover "off" would start a
41
+ // reinstall switched off.
42
+ const opencodeFlagPath = path.join(temp, 'config-home', 'opencode', '.ponytail-active');
43
+ fs.mkdirSync(path.dirname(opencodeFlagPath), { recursive: true });
44
+ fs.writeFileSync(opencodeFlagPath, 'off');
45
+
46
+ const nudgeFlagPath = path.join(claudeDir, '.ponytail-statusline-nudged');
47
+ fs.writeFileSync(nudgeFlagPath, '');
48
+
49
+ const configDir = path.join(temp, 'config-home', 'ponytail');
50
+ fs.mkdirSync(configDir, { recursive: true });
51
+ const configPath = path.join(configDir, 'config.json');
52
+ fs.writeFileSync(configPath, JSON.stringify({ defaultMode: 'ultra' }));
53
+
54
+ // #1032: the statusline script copy kept in the config dir.
55
+ const statuslineCopyPath = path.join(claudeDir, 'ponytail-statusline.sh');
56
+ fs.writeFileSync(statuslineCopyPath, '#!/usr/bin/env bash\n');
57
+ const statuslinePs1CopyPath = path.join(claudeDir, 'ponytail-statusline.ps1');
58
+ fs.writeFileSync(statuslinePs1CopyPath, '');
59
+
60
+ const settingsPath = path.join(claudeDir, 'settings.json');
61
+ fs.writeFileSync(settingsPath, JSON.stringify({
62
+ statusLine: { type: 'command', command: 'bash /some/path/ponytail-statusline.sh' },
63
+ }));
64
+
65
+ // Cursor (#817): the Cursor mode flag goes too, and ~/.cursor/hooks.json loses
66
+ // only ponytail's entries; the user's other hooks stay.
67
+ const cursorDir = path.join(home, '.cursor');
68
+ fs.mkdirSync(cursorDir, { recursive: true });
69
+ const cursorFlagPath = path.join(cursorDir, '.ponytail-active');
70
+ fs.writeFileSync(cursorFlagPath, 'lite');
71
+ const cursorHooksPath = path.join(cursorDir, 'hooks.json');
72
+ fs.writeFileSync(cursorHooksPath, JSON.stringify({
73
+ version: 1,
74
+ hooks: {
75
+ sessionStart: [
76
+ { command: './hooks/mine.sh' },
77
+ { command: 'node "/p/ponytail/hooks/ponytail-activate.js"', timeout: 5 },
78
+ ],
79
+ beforeSubmitPrompt: [{ command: 'node "/p/ponytail/hooks/ponytail-mode-tracker.js"', timeout: 5 }],
80
+ },
81
+ }));
82
+
83
+ const env = {
84
+ HOME: home,
85
+ USERPROFILE: home,
86
+ XDG_CONFIG_HOME: path.join(temp, 'config-home'),
87
+ };
88
+
89
+ let result = runUninstall(env);
90
+ assert.equal(result.status, 0, result.stderr);
91
+ assert.equal(fs.existsSync(flagPath), false, 'mode flag must be removed');
92
+ assert.equal(fs.existsSync(qoderFlagPath), false, 'Qoder mode flag must be removed');
93
+ assert.equal(fs.existsSync(opencodeFlagPath), false, 'OpenCode mode flag must be removed');
94
+ assert.equal(fs.existsSync(qoderCustomFlagPath), true, 'default Qoder uninstall must not touch a separate custom config');
95
+ assert.equal(fs.existsSync(nudgeFlagPath), false, 'statusline nudge flag must be removed');
96
+ assert.equal(fs.existsSync(configPath), false, 'config file must be removed');
97
+ assert.equal(fs.existsSync(cursorFlagPath), false, 'Cursor mode flag must be removed');
98
+ assert.equal(fs.existsSync(statuslineCopyPath), false, 'statusline script copy must be removed (#1032)');
99
+ assert.equal(fs.existsSync(statuslinePs1CopyPath), false, 'statusline .ps1 copy must be removed (#1032)');
100
+ assert.deepEqual(
101
+ JSON.parse(fs.readFileSync(cursorHooksPath, 'utf8')),
102
+ { version: 1, hooks: { sessionStart: [{ command: './hooks/mine.sh' }] } },
103
+ "only ponytail's entries may leave ~/.cursor/hooks.json",
104
+ );
105
+
106
+ const settingsAfter = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
107
+ assert.equal(
108
+ settingsAfter.statusLine,
109
+ undefined,
110
+ 'ponytail statusLine entry must be removed',
111
+ );
112
+
113
+ // A user's own, unrelated statusLine must survive untouched.
114
+ fs.writeFileSync(settingsPath, JSON.stringify({
115
+ statusLine: { type: 'command', command: 'bash ~/my-custom-statusline.sh' },
116
+ }));
117
+
118
+ result = runUninstall(env);
119
+ assert.equal(result.status, 0, result.stderr);
120
+ const settingsAfter2 = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
121
+ assert.equal(
122
+ settingsAfter2.statusLine.command,
123
+ 'bash ~/my-custom-statusline.sh',
124
+ "a user's own statusLine must not be touched",
125
+ );
126
+
127
+ // A user command that merely contains ponytail's script name must also survive.
128
+ fs.writeFileSync(settingsPath, JSON.stringify({
129
+ statusLine: { type: 'command', command: 'bash ~/my-ponytail-statusline.sh' },
130
+ }));
131
+
132
+ result = runUninstall(env);
133
+ assert.equal(result.status, 0, result.stderr);
134
+ const settingsAfterSimilarName = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
135
+ assert.equal(
136
+ settingsAfterSimilarName.statusLine.command,
137
+ 'bash ~/my-ponytail-statusline.sh',
138
+ "a similarly named user statusLine must not be touched",
139
+ );
140
+
141
+ // #374: a combined statusline (another plugin && ponytail) must keep the other
142
+ // plugin's part — uninstall must not nuke the whole command or leave a husk.
143
+ fs.writeFileSync(settingsPath, JSON.stringify({
144
+ statusLine: { type: 'command', command: 'bash ~/caveman-statusline.sh && bash /p/ponytail-statusline.sh' },
145
+ }));
146
+
147
+ result = runUninstall(env);
148
+ assert.equal(result.status, 0, result.stderr);
149
+ const settingsAfter3 = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
150
+ assert.equal(
151
+ settingsAfter3.statusLine.command,
152
+ 'bash ~/caveman-statusline.sh',
153
+ 'a combined statusLine must keep the non-ponytail command',
154
+ );
155
+
156
+ result = runUninstall({ ...env, QODER_CONFIG_DIR: qoderCustomDir });
157
+ assert.equal(result.status, 0, result.stderr);
158
+ assert.equal(fs.existsSync(qoderCustomFlagPath), false, 'Qoder custom config mode flag must be removed');
159
+
160
+ // #434: a malformed settings.json must not crash the script mid-cleanup. It
161
+ // can't be safely edited, so uninstall warns and leaves the file byte-for-byte
162
+ // intact instead of throwing a SyntaxError after other state was already removed.
163
+ const malformedSettings = '{ "statusLine": { "command": "ponytail-statusline.sh", broken';
164
+ fs.writeFileSync(settingsPath, malformedSettings);
165
+
166
+ result = runUninstall(env);
167
+ assert.equal(
168
+ result.status,
169
+ 0,
170
+ `expected exit 0 on malformed settings.json, got:\n${result.stdout}${result.stderr}`,
171
+ );
172
+ assert.ok(
173
+ /malformed/i.test(result.stdout + result.stderr),
174
+ 'must warn that the statusLine entry could not be removed',
175
+ );
176
+ assert.equal(
177
+ fs.readFileSync(settingsPath, 'utf8'),
178
+ malformedSettings,
179
+ 'malformed settings.json must be left unchanged',
180
+ );
181
+
182
+ // A malformed ~/.cursor/hooks.json must not crash the script either (#817).
183
+ const malformedHooks = '{ "version": 1, "hooks": { broken';
184
+ fs.writeFileSync(cursorHooksPath, malformedHooks);
185
+
186
+ result = runUninstall(env);
187
+ assert.equal(result.status, 0, result.stderr);
188
+ assert.ok(
189
+ /hooks\.json is malformed/.test(result.stdout + result.stderr),
190
+ 'must warn that the Cursor hook entries could not be removed',
191
+ );
192
+ assert.equal(
193
+ fs.readFileSync(cursorHooksPath, 'utf8'),
194
+ malformedHooks,
195
+ 'malformed hooks.json must be left unchanged',
196
+ );
197
+
198
+ // Running on an already-clean machine must not throw.
199
+ result = runUninstall({ HOME: path.join(temp, 'home-empty'), USERPROFILE: path.join(temp, 'home-empty') });
200
+ assert.equal(result.status, 0, result.stderr);
201
+
202
+ console.log('uninstall script checks passed');
File without changes
File without changes
File without changes