claws-code 0.8.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 (180) hide show
  1. package/.claude/commands/claws-auto.md +90 -0
  2. package/.claude/commands/claws-bin.md +28 -0
  3. package/.claude/commands/claws-cleanup.md +28 -0
  4. package/.claude/commands/claws-do.md +82 -0
  5. package/.claude/commands/claws-fix.md +40 -0
  6. package/.claude/commands/claws-goal.md +111 -0
  7. package/.claude/commands/claws-help.md +54 -0
  8. package/.claude/commands/claws-plan.md +103 -0
  9. package/.claude/commands/claws-report.md +29 -0
  10. package/.claude/commands/claws-status.md +37 -0
  11. package/.claude/commands/claws-update.md +32 -0
  12. package/.claude/commands/claws.md +64 -0
  13. package/.claude/rules/claws-default-behavior.md +76 -0
  14. package/.claude/settings.json +112 -0
  15. package/.claude/settings.local.json +19 -0
  16. package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
  17. package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
  18. package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
  19. package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
  20. package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
  21. package/CHANGELOG.md +1949 -0
  22. package/LICENSE +21 -0
  23. package/README.md +420 -0
  24. package/bin/cli.js +84 -0
  25. package/cli.js +223 -0
  26. package/docs/ARCHITECTURE.md +511 -0
  27. package/docs/event-protocol.md +588 -0
  28. package/docs/features.md +562 -0
  29. package/docs/guide.md +891 -0
  30. package/docs/index.html +716 -0
  31. package/docs/protocol.md +323 -0
  32. package/extension/.vscodeignore +15 -0
  33. package/extension/CHANGELOG.md +1906 -0
  34. package/extension/LICENSE +21 -0
  35. package/extension/README.md +137 -0
  36. package/extension/docs/features.md +424 -0
  37. package/extension/docs/protocol.md +197 -0
  38. package/extension/esbuild.mjs +25 -0
  39. package/extension/icon.png +0 -0
  40. package/extension/native/.metadata.json +10 -0
  41. package/extension/native/node-pty/LICENSE +69 -0
  42. package/extension/native/node-pty/README.md +165 -0
  43. package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
  44. package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
  45. package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
  46. package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
  47. package/extension/native/node-pty/lib/index.js +52 -0
  48. package/extension/native/node-pty/lib/index.js.map +1 -0
  49. package/extension/native/node-pty/lib/interfaces.js +7 -0
  50. package/extension/native/node-pty/lib/interfaces.js.map +1 -0
  51. package/extension/native/node-pty/lib/shared/conout.js +11 -0
  52. package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
  53. package/extension/native/node-pty/lib/terminal.js +190 -0
  54. package/extension/native/node-pty/lib/terminal.js.map +1 -0
  55. package/extension/native/node-pty/lib/types.js +7 -0
  56. package/extension/native/node-pty/lib/types.js.map +1 -0
  57. package/extension/native/node-pty/lib/unixTerminal.js +346 -0
  58. package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
  59. package/extension/native/node-pty/lib/utils.js +39 -0
  60. package/extension/native/node-pty/lib/utils.js.map +1 -0
  61. package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
  62. package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
  63. package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
  64. package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
  65. package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
  66. package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
  67. package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  68. package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
  69. package/extension/native/node-pty/package.json +64 -0
  70. package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  71. package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  72. package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  73. package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  74. package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  75. package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  76. package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  77. package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  78. package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
  79. package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
  80. package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
  81. package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  82. package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  83. package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  84. package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  85. package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
  86. package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
  87. package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
  88. package/extension/package-lock.json +605 -0
  89. package/extension/package.json +343 -0
  90. package/extension/scripts/bundle-native.mjs +104 -0
  91. package/extension/scripts/deploy-dev.mjs +60 -0
  92. package/extension/src/ansi-strip.ts +52 -0
  93. package/extension/src/backends/vscode/claws-pty.ts +483 -0
  94. package/extension/src/backends/vscode/status-bar.ts +99 -0
  95. package/extension/src/backends/vscode/vscode-backend.ts +282 -0
  96. package/extension/src/capture-store.ts +125 -0
  97. package/extension/src/event-log.ts +629 -0
  98. package/extension/src/event-schemas.ts +478 -0
  99. package/extension/src/extension.js +492 -0
  100. package/extension/src/extension.ts +873 -0
  101. package/extension/src/lifecycle-engine.ts +60 -0
  102. package/extension/src/lifecycle-rules.ts +171 -0
  103. package/extension/src/lifecycle-store.ts +506 -0
  104. package/extension/src/peer-registry.ts +176 -0
  105. package/extension/src/pipeline-registry.ts +82 -0
  106. package/extension/src/platform.ts +64 -0
  107. package/extension/src/protocol.ts +532 -0
  108. package/extension/src/server-config.ts +98 -0
  109. package/extension/src/server.ts +2210 -0
  110. package/extension/src/task-registry.ts +51 -0
  111. package/extension/src/terminal-backend.ts +211 -0
  112. package/extension/src/terminal-manager.ts +395 -0
  113. package/extension/src/topic-registry.ts +70 -0
  114. package/extension/src/topic-utils.ts +46 -0
  115. package/extension/src/transport.ts +45 -0
  116. package/extension/src/uninstall-cleanup.ts +232 -0
  117. package/extension/src/wave-registry.ts +314 -0
  118. package/extension/src/websocket-transport.ts +153 -0
  119. package/extension/tsconfig.json +23 -0
  120. package/lib/capabilities.js +145 -0
  121. package/lib/dry-run.js +43 -0
  122. package/lib/install.js +1018 -0
  123. package/lib/mcp-setup.js +92 -0
  124. package/lib/platform.js +240 -0
  125. package/lib/preflight.js +152 -0
  126. package/lib/shell-hook.js +343 -0
  127. package/lib/uninstall.js +162 -0
  128. package/lib/verify.js +166 -0
  129. package/mcp_server.js +3529 -0
  130. package/package.json +48 -0
  131. package/rules/claws-default-behavior.md +72 -0
  132. package/scripts/_helpers/atomic-file.mjs +137 -0
  133. package/scripts/_helpers/fix-repair.js +64 -0
  134. package/scripts/_helpers/json-safe.mjs +218 -0
  135. package/scripts/bump-version.sh +84 -0
  136. package/scripts/codegen/gen-docs.mjs +61 -0
  137. package/scripts/codegen/gen-json-schema.mjs +62 -0
  138. package/scripts/codegen/gen-mcp-tools.mjs +358 -0
  139. package/scripts/codegen/gen-types.mjs +172 -0
  140. package/scripts/codegen/index.mjs +42 -0
  141. package/scripts/dev-hooks/check-extension-dirs.js +77 -0
  142. package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
  143. package/scripts/dev-hooks/check-stale-main.js +55 -0
  144. package/scripts/dev-hooks/check-tag-pushed.js +51 -0
  145. package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
  146. package/scripts/dev-vsix-install.sh +60 -0
  147. package/scripts/fix.sh +702 -0
  148. package/scripts/gen-client-types.mjs +81 -0
  149. package/scripts/git-hooks/pre-commit +31 -0
  150. package/scripts/hooks/lifecycle-state.js +61 -0
  151. package/scripts/hooks/package.json +4 -0
  152. package/scripts/hooks/post-tool-use-claws.js +292 -0
  153. package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
  154. package/scripts/hooks/pre-tool-use-claws.js +206 -0
  155. package/scripts/hooks/session-start-claws.js +97 -0
  156. package/scripts/hooks/stop-claws.js +88 -0
  157. package/scripts/inject-claude-md.js +205 -0
  158. package/scripts/inject-dev-hooks.js +96 -0
  159. package/scripts/inject-global-claude-md.js +140 -0
  160. package/scripts/inject-settings-hooks.js +370 -0
  161. package/scripts/install.ps1 +146 -0
  162. package/scripts/install.sh +1729 -0
  163. package/scripts/monitor-arm-watch.js +155 -0
  164. package/scripts/rebuild-node-pty.sh +245 -0
  165. package/scripts/report.sh +232 -0
  166. package/scripts/shell-hook.fish +164 -0
  167. package/scripts/shell-hook.ps1 +33 -0
  168. package/scripts/shell-hook.sh +232 -0
  169. package/scripts/stream-events.js +399 -0
  170. package/scripts/terminal-wrapper.sh +36 -0
  171. package/scripts/test-enforcement.sh +132 -0
  172. package/scripts/test-install.sh +174 -0
  173. package/scripts/test-installer-parity.sh +135 -0
  174. package/scripts/test-template-enforcement.sh +76 -0
  175. package/scripts/uninstall.sh +143 -0
  176. package/scripts/update.sh +337 -0
  177. package/scripts/verify-release.sh +323 -0
  178. package/scripts/verify-wrapped.sh +194 -0
  179. package/templates/CLAUDE.global.md +135 -0
  180. package/templates/CLAUDE.project.md +37 -0
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import { matchTopic } from './topic-utils';
3
+ import {
4
+ WorkerBootV1, WorkerPhaseV1, WorkerEventV1, WorkerHeartbeatV1, WorkerCompleteV1,
5
+ CmdApproveV1, CmdRejectV1, CmdAbortV1, CmdPauseV1, CmdResumeV1,
6
+ CmdSetPhaseV1, CmdSpawnV1, CmdInjectTextV1,
7
+ SystemPeerJoinedV1, SystemPeerLeftV1, SystemPeerStaleV1,
8
+ SystemGateFiredV1, SystemBudgetWarningV1, SystemMalformedReceivedV1,
9
+ VehicleStateV1, VehicleContentV1, CommandStartV1, CommandEndV1,
10
+ CmdAckV1, PipelineStepV1, RpcRequestV1, RpcResponseV1, WaveHarvestedV1,
11
+ TerminalClosedV1,
12
+ } from './event-schemas';
13
+
14
+ export { matchTopic };
15
+
16
+ export const TOPIC_REGISTRY: ReadonlyArray<{ pattern: string; schema: z.ZodTypeAny }> = [
17
+ { pattern: 'worker.*.boot', schema: WorkerBootV1 },
18
+ { pattern: 'worker.*.phase', schema: WorkerPhaseV1 },
19
+ { pattern: 'worker.*.event', schema: WorkerEventV1 },
20
+ { pattern: 'worker.*.heartbeat', schema: WorkerHeartbeatV1 },
21
+ { pattern: 'worker.*.complete', schema: WorkerCompleteV1 },
22
+ { pattern: 'cmd.*.approve', schema: CmdApproveV1 },
23
+ { pattern: 'cmd.*.reject', schema: CmdRejectV1 },
24
+ { pattern: 'cmd.*.abort', schema: CmdAbortV1 },
25
+ { pattern: 'cmd.*.pause', schema: CmdPauseV1 },
26
+ { pattern: 'cmd.*.resume', schema: CmdResumeV1 },
27
+ { pattern: 'cmd.*.set_phase', schema: CmdSetPhaseV1 },
28
+ { pattern: 'cmd.*.spawn', schema: CmdSpawnV1 },
29
+ { pattern: 'cmd.*.inject_text', schema: CmdInjectTextV1 },
30
+ { pattern: 'system.peer.joined', schema: SystemPeerJoinedV1 },
31
+ { pattern: 'system.peer.left', schema: SystemPeerLeftV1 },
32
+ { pattern: 'system.peer.stale', schema: SystemPeerStaleV1 },
33
+ { pattern: 'system.gate.fired', schema: SystemGateFiredV1 },
34
+ { pattern: 'system.budget.warning', schema: SystemBudgetWarningV1 },
35
+ { pattern: 'system.malformed.received', schema: SystemMalformedReceivedV1 },
36
+ { pattern: 'vehicle.*.state', schema: VehicleStateV1 },
37
+ { pattern: 'vehicle.*.created', schema: VehicleStateV1 },
38
+ { pattern: 'vehicle.*.closed', schema: VehicleStateV1 },
39
+ { pattern: 'vehicle.*.content', schema: VehicleContentV1 },
40
+ { pattern: 'command.*.start', schema: CommandStartV1 },
41
+ { pattern: 'command.*.end', schema: CommandEndV1 },
42
+ { pattern: 'command.*.timeout', schema: CommandEndV1 },
43
+ { pattern: 'error.*.crash', schema: z.object({
44
+ terminalId: z.string().min(1),
45
+ exitCode: z.number().int().nullable(),
46
+ ts: z.string().datetime(),
47
+ }) },
48
+ { pattern: 'wave.*.harvested', schema: WaveHarvestedV1 },
49
+ { pattern: 'wave.*.violation', schema: z.record(z.unknown()) },
50
+ { pattern: 'wave.*.orphan_detected', schema: z.record(z.unknown()) },
51
+ { pattern: 'wave.**', schema: z.record(z.unknown()) },
52
+ { pattern: 'cmd.*.ack', schema: CmdAckV1 },
53
+ { pattern: 'pipeline.*.step.*', schema: PipelineStepV1 },
54
+ { pattern: 'pipeline.*.created', schema: z.record(z.unknown()) },
55
+ { pattern: 'pipeline.*.closed', schema: z.record(z.unknown()) },
56
+ { pattern: 'rpc.*.request', schema: RpcRequestV1 },
57
+ { pattern: 'rpc.response.**', schema: RpcResponseV1 },
58
+ { pattern: 'system.terminal.closed', schema: TerminalClosedV1 },
59
+ ];
60
+
61
+ /**
62
+ * Returns the Zod data schema for the given topic, or null if no schema
63
+ * is registered for the topic pattern.
64
+ */
65
+ export function schemaForTopic(topic: string): z.ZodTypeAny | null {
66
+ for (const entry of TOPIC_REGISTRY) {
67
+ if (matchTopic(topic, entry.pattern)) return entry.schema;
68
+ }
69
+ return null;
70
+ }
@@ -0,0 +1,46 @@
1
+ // Standalone topic-pattern matching utilities.
2
+ // Imported by both peer-registry.ts and topic-registry.ts to avoid a
3
+ // circular dependency that would arise if topic-registry imported from
4
+ // peer-registry while server.ts imports both.
5
+
6
+ /**
7
+ * Match a concrete dot-delimited topic against a subscription pattern.
8
+ *
9
+ * Rules (segments separated by `.`):
10
+ * - `*` matches exactly one segment
11
+ * - `**` matches one or more segments (greedy; at least one segment)
12
+ * - any other segment must match literally
13
+ *
14
+ * Examples:
15
+ * matchTopic('task.started.p1', 'task.*.p1') === true
16
+ * matchTopic('task.started.p1', 'task.**') === true
17
+ * matchTopic('task.started', 'task.**') === true
18
+ * matchTopic('task', 'task.**') === false
19
+ * matchTopic('worker.online', 'worker.*') === true
20
+ * matchTopic('worker.online.p1','worker.*') === false
21
+ */
22
+ export function matchTopic(topic: string, pattern: string): boolean {
23
+ const t = topic.split('.');
24
+ const p = pattern.split('.');
25
+ return matchSegments(t, 0, p, 0);
26
+ }
27
+
28
+ function matchSegments(t: string[], ti: number, p: string[], pi: number): boolean {
29
+ while (pi < p.length) {
30
+ const seg = p[pi];
31
+ if (seg === '**') {
32
+ if (pi === p.length - 1) {
33
+ return ti < t.length;
34
+ }
35
+ for (let k = ti + 1; k <= t.length; k++) {
36
+ if (matchSegments(t, k, p, pi + 1)) return true;
37
+ }
38
+ return false;
39
+ }
40
+ if (ti >= t.length) return false;
41
+ if (seg !== '*' && seg !== t[ti]) return false;
42
+ ti++;
43
+ pi++;
44
+ }
45
+ return ti === t.length;
46
+ }
@@ -0,0 +1,45 @@
1
+ // extension/src/transport.ts
2
+ // Cross-platform IPC endpoint computation for Claws.
3
+ // On Unix (darwin/linux): workspace/.claws/claws.sock
4
+ // On Windows: \\.\pipe\claws-<sha256[0:8] of workspace root>
5
+
6
+ import * as crypto from 'crypto';
7
+ import * as path from 'path';
8
+ import type { NodePlatform } from './platform';
9
+
10
+ export type Endpoint = string;
11
+
12
+ /**
13
+ * Compute the IPC endpoint for a given workspace root.
14
+ *
15
+ * Decision 1 (v0.8 blueprint): Windows uses named pipes because Unix sockets
16
+ * are not reliably available on Windows. The sha256[0:8] hash provides a
17
+ * stable, collision-resistant pipe name that encodes workspace identity into
18
+ * the flat \\.\pipe\ namespace. Windows paths are lowercased before hashing
19
+ * because NTFS is case-insensitive.
20
+ *
21
+ * @param workspaceRoot absolute path to the VS Code workspace folder
22
+ * @param platform injectable for testing; defaults to process.platform
23
+ */
24
+ export function getServerEndpoint(
25
+ workspaceRoot: string,
26
+ platform: NodePlatform | string = process.platform,
27
+ ): Endpoint {
28
+ if (platform === 'win32') {
29
+ const hash = crypto
30
+ .createHash('sha256')
31
+ .update(workspaceRoot.toLowerCase())
32
+ .digest('hex')
33
+ .slice(0, 8);
34
+ return `\\\\.\\pipe\\claws-${hash}`;
35
+ }
36
+ return path.join(workspaceRoot, '.claws', 'claws.sock');
37
+ }
38
+
39
+ /**
40
+ * True when the endpoint is a Windows named pipe rather than a filesystem path.
41
+ * Used to guard fs.unlink / fs.chmod calls that are no-ops for named pipes.
42
+ */
43
+ export function isNamedPipe(endpoint: Endpoint): boolean {
44
+ return endpoint.startsWith('\\\\.\\pipe\\') || endpoint.startsWith('//./pipe/');
45
+ }
@@ -0,0 +1,232 @@
1
+ // Uninstall-cleanup command implementation.
2
+ //
3
+ // Inventories Claws-installed files across the currently-open workspace
4
+ // folders and, on explicit per-folder confirmation, removes only what was
5
+ // actually installed. Never touches files Claws didn't put there (edits to
6
+ // JSON / markdown surgically strip only the `claws` entries).
7
+
8
+ import * as vscode from 'vscode';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+
12
+ // M-30: atomic write helper (tmp + renameSync) — prevents partial file on kill mid-write.
13
+ // Inline implementation (no cross-package import needed) — mirrors atomic-file.mjs.
14
+ // F3: open+writeSync+fsyncSync+close before rename for durability on power-cut.
15
+ let _atomicNonce = 0;
16
+ function writeAtomic(filePath: string, content: string): void {
17
+ const tmp = `${filePath}.claws-tmp.${process.pid}-${++_atomicNonce}`;
18
+ let _fd: number | null = null;
19
+ try {
20
+ _fd = fs.openSync(tmp, 'w', 0o644);
21
+ fs.writeSync(_fd, content);
22
+ fs.fsyncSync(_fd);
23
+ fs.closeSync(_fd);
24
+ _fd = null;
25
+ fs.renameSync(tmp, filePath);
26
+ } catch (err) {
27
+ if (_fd !== null) { try { fs.closeSync(_fd); } catch { /* ignore */ } }
28
+ try { fs.unlinkSync(tmp); } catch { /* ignore */ }
29
+ throw err;
30
+ }
31
+ }
32
+
33
+ export interface CleanupAction {
34
+ action: 'rmdir' | 'rm' | 'edit-json' | 'edit-markdown';
35
+ path: string;
36
+ /** Extra context for edit-* actions. */
37
+ note?: string;
38
+ }
39
+
40
+ export interface CleanupResult {
41
+ removed: number;
42
+ failed: number;
43
+ }
44
+
45
+ /**
46
+ * Register the `claws.uninstallCleanup` command. The command:
47
+ * 1. Shows a modal warning
48
+ * 2. For each currently-open workspace folder, plans + confirms + executes
49
+ * 3. Prints a summary to the Output channel
50
+ * 4. Guides the user to uninstall the extension itself manually
51
+ */
52
+ export function registerUninstallCleanupCommand(
53
+ context: vscode.ExtensionContext,
54
+ logger: (msg: string) => void,
55
+ showOutput: () => void,
56
+ ): void {
57
+ context.subscriptions.push(
58
+ vscode.commands.registerCommand('claws.uninstallCleanup', async () => {
59
+ const confirm = await vscode.window.showInformationMessage(
60
+ 'Claws: Uninstall Cleanup — this will remove Claws-installed files from your currently-open workspace folders. The VS Code extension itself must be uninstalled manually afterwards. Continue?',
61
+ { modal: true },
62
+ 'Continue',
63
+ 'Cancel',
64
+ );
65
+ if (confirm !== 'Continue') return;
66
+
67
+ const folders = vscode.workspace.workspaceFolders ?? [];
68
+ if (folders.length === 0) {
69
+ vscode.window.showWarningMessage('Claws: no workspace folders open. Nothing to clean.');
70
+ return;
71
+ }
72
+
73
+ showOutput();
74
+ logger('');
75
+ logger('── Claws: Uninstall Cleanup ──');
76
+
77
+ const summary: string[] = [];
78
+ for (const folder of folders) {
79
+ const root = folder.uri.fsPath;
80
+ const plan = planCleanup(root);
81
+ if (plan.length === 0) {
82
+ logger(`[cleanup] ${root}: nothing to remove`);
83
+ continue;
84
+ }
85
+ const detail = plan.map((p) => ` ${p.action}: ${p.path}`).join('\n');
86
+ logger(`[cleanup] ${root}: planned removals:\n${detail}`);
87
+ const proceed = await vscode.window.showWarningMessage(
88
+ `Claws: clean up files in ${path.basename(root)}?\n\n${plan.map((p) => `${p.action}: ${path.relative(root, p.path)}`).join('\n')}`,
89
+ { modal: true },
90
+ 'Remove',
91
+ 'Skip',
92
+ );
93
+ if (proceed !== 'Remove') {
94
+ logger(`[cleanup] ${root}: SKIPPED by user`);
95
+ continue;
96
+ }
97
+ const results = executeCleanup(plan, logger);
98
+ summary.push(`${root}: ${results.removed} removed, ${results.failed} failed`);
99
+ }
100
+
101
+ logger('');
102
+ logger('── cleanup summary ──');
103
+ if (summary.length === 0) {
104
+ logger('no folders were cleaned');
105
+ } else {
106
+ for (const s of summary) logger(` ${s}`);
107
+ }
108
+ logger('');
109
+ logger('Next step: uninstall the "Claws" extension from the VS Code Extensions panel.');
110
+ vscode.window.showInformationMessage(
111
+ 'Claws cleanup complete. Uninstall the VS Code extension manually from the Extensions panel to finish.',
112
+ );
113
+ }),
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Build a plan of cleanup actions for a single workspace folder. Returns an
119
+ * empty array if nothing Claws-installed is detected — the command uses this
120
+ * to skip folder confirmation prompts for folders with nothing to remove.
121
+ */
122
+ export function planCleanup(root: string): CleanupAction[] {
123
+ const plan: CleanupAction[] = [];
124
+ const push = (a: CleanupAction) => plan.push(a);
125
+
126
+ const mcpJson = path.join(root, '.mcp.json');
127
+ if (fs.existsSync(mcpJson)) {
128
+ // Only target when it actually has a claws entry.
129
+ try {
130
+ const raw = fs.readFileSync(mcpJson, 'utf8');
131
+ const parsed = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };
132
+ if (parsed.mcpServers && 'claws' in parsed.mcpServers) {
133
+ push({ action: 'edit-json', path: mcpJson, note: 'drop mcpServers.claws' });
134
+ }
135
+ } catch { /* malformed — skip */ }
136
+ }
137
+
138
+ const clawsBin = path.join(root, '.claws-bin');
139
+ if (fs.existsSync(clawsBin)) push({ action: 'rmdir', path: clawsBin });
140
+
141
+ const cmdsDir = path.join(root, '.claude', 'commands');
142
+ if (fs.existsSync(cmdsDir)) {
143
+ for (const entry of fs.readdirSync(cmdsDir)) {
144
+ if (entry.startsWith('claws-') && entry.endsWith('.md')) {
145
+ push({ action: 'rm', path: path.join(cmdsDir, entry) });
146
+ }
147
+ }
148
+ }
149
+
150
+ const rulesFile = path.join(root, '.claude', 'rules', 'claws-default-behavior.md');
151
+ if (fs.existsSync(rulesFile)) push({ action: 'rm', path: rulesFile });
152
+
153
+ for (const skill of ['claws-orchestration-engine', 'claws-prompt-templates', 'claws-wave-lead', 'claws-wave-subworker']) {
154
+ const p = path.join(root, '.claude', 'skills', skill);
155
+ if (fs.existsSync(p)) push({ action: 'rmdir', path: p });
156
+ }
157
+
158
+ const extensionsJson = path.join(root, '.vscode', 'extensions.json');
159
+ if (fs.existsSync(extensionsJson)) {
160
+ try {
161
+ const raw = fs.readFileSync(extensionsJson, 'utf8');
162
+ const parsed = JSON.parse(raw) as { recommendations?: unknown };
163
+ const recs = Array.isArray(parsed.recommendations) ? parsed.recommendations as string[] : [];
164
+ if (recs.includes('neunaha.claws')) {
165
+ push({ action: 'edit-json', path: extensionsJson, note: 'drop recommendations[neunaha.claws]' });
166
+ }
167
+ } catch { /* malformed — skip */ }
168
+ }
169
+
170
+ const claudeMd = path.join(root, 'CLAUDE.md');
171
+ if (fs.existsSync(claudeMd)) {
172
+ try {
173
+ const raw = fs.readFileSync(claudeMd, 'utf8');
174
+ if (raw.includes('<!-- CLAWS:BEGIN -->') && raw.includes('<!-- CLAWS:END -->')) {
175
+ push({ action: 'edit-markdown', path: claudeMd, note: 'strip CLAWS fenced block' });
176
+ }
177
+ } catch { /* ignore */ }
178
+ }
179
+
180
+ return plan;
181
+ }
182
+
183
+ /**
184
+ * Execute a cleanup plan. Each action runs in a try/catch so one failure
185
+ * does not abort the rest. Logs each operation's outcome and returns a
186
+ * tally for the summary line.
187
+ */
188
+ export function executeCleanup(
189
+ plan: CleanupAction[],
190
+ logger: (msg: string) => void,
191
+ ): CleanupResult {
192
+ let removed = 0;
193
+ let failed = 0;
194
+ for (const action of plan) {
195
+ try {
196
+ if (action.action === 'rm') {
197
+ fs.unlinkSync(action.path);
198
+ logger(`[cleanup] ✓ rm ${action.path}`);
199
+ } else if (action.action === 'rmdir') {
200
+ fs.rmSync(action.path, { recursive: true, force: true });
201
+ logger(`[cleanup] ✓ rmdir ${action.path}`);
202
+ } else if (action.action === 'edit-json') {
203
+ const raw = fs.readFileSync(action.path, 'utf8');
204
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
205
+ if (path.basename(action.path) === '.mcp.json') {
206
+ const servers = parsed.mcpServers as Record<string, unknown> | undefined;
207
+ if (servers) delete servers.claws;
208
+ } else if (path.basename(action.path) === 'extensions.json') {
209
+ const recs = parsed.recommendations as string[] | undefined;
210
+ if (Array.isArray(recs)) {
211
+ parsed.recommendations = recs.filter((r) => r !== 'neunaha.claws');
212
+ }
213
+ }
214
+ writeAtomic(action.path, JSON.stringify(parsed, null, 2) + '\n');
215
+ logger(`[cleanup] ✓ edit-json ${action.path} (${action.note})`);
216
+ } else if (action.action === 'edit-markdown') {
217
+ const raw = fs.readFileSync(action.path, 'utf8');
218
+ const stripped = raw.replace(
219
+ /<!-- CLAWS:BEGIN -->[\s\S]*?<!-- CLAWS:END -->\n?/g,
220
+ '',
221
+ );
222
+ writeAtomic(action.path, stripped);
223
+ logger(`[cleanup] ✓ edit-markdown ${action.path} (${action.note})`);
224
+ }
225
+ removed += 1;
226
+ } catch (err) {
227
+ logger(`[cleanup] ✗ ${action.action} ${action.path} — ${(err as Error).message}`);
228
+ failed += 1;
229
+ }
230
+ }
231
+ return { removed, failed };
232
+ }