RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/CLAUDE.md/anneschuth/claude-threads

CLAUDE.md

CLAUDE.md
CLAUDE.mdroot

Quality

77/100

Scores the file, not the repository.

Length

5,784 words

87 headings · 17 code blocks

Repository

37

— · pushed 0 days ago

Last changed

3 days ago

First indexed 3 days ago.
anneschuth/claude-threads/CLAUDE.mdRawGitHub
1# Claude Code Instructions for claude-threads
2 
3## What This Project Does
4 
5This is a multi-platform bot that lets users interact with Claude Code through chat platforms. When someone @mentions the bot in a channel, it spawns a Claude Code CLI session in a configured working directory and streams all output to a thread. The user can continue the conversation by replying in the thread.
6 
7**Currently Supported Platforms:**
8- Mattermost (full support)
9- Slack (full support)
10 
11**Key Features:**
12- Real-time streaming of Claude responses to chat platforms
13- **Multi-platform support** - connect to multiple Mattermost/Slack instances simultaneously
14- **Multiple concurrent sessions** - one per thread, across all platforms
15- **Session persistence** - sessions resume automatically after bot restart
16- **Session collaboration** - `!invite @user` to temporarily allow users in a session
17- **Message approval** - unauthorized users can request approval for their messages
18- **Thread context prompt** - when starting a session mid-thread, offers to include previous conversation context
19- Interactive permission approval via emoji reactions
20- Plan approval and question answering via reactions
21- Task list display with live updates
22- Code diffs and file previews
23- Multi-user access control
24- Automatic idle session cleanup
25- **Permalink follower (`read_post` MCP tool)** - Claude can resolve a Mattermost or Slack permalink to its content (and optional thread context) inside the bot's own channel
26 
27## Contribution Conventions
28 
29- **Pull request titles and bodies are written in English.** This is an open-source project with an international audience, so PRs stay in English even though commit messages may be in Dutch. Keep the CHANGELOG in English as well.
30 
31## Architecture Overview
32 
33```
34┌─────────────────────────────────────────────────────────────────┐
35│ Chat Platform │
36│ ┌──────────────┐ ┌──────────────────────┐ │
37│ │ User message │ ───WebSocket───▶ │ PlatformClient │ │
38│ │ + reactions │ ◀─────────────── │ (Mattermost/Slack) │ │
39│ └──────────────┘ └──────────┬───────────┘ │
40└─────────────────────────────────────────────────┼───────────────┘
41 │
42 ┌───────────────────────────┴────────────────┐
43 │ SessionManager │
44 │ - Orchestrates session lifecycle │
45 │ - Delegates to specialized modules │
46 │ - sessions: Map<sessionId, Session> │
47 │ - postIndex: Map<postId, threadId> │
48 └───────────────────────────┬────────────────┘
49 │
50 ┌───────────────────┼───────────────────┐
51 │ │ │
52 ▼ ▼ ▼
53 ┌───────────┐ ┌───────────┐ ┌───────────┐
54 │ Session │ │ Session │ │ Session │
55 │ (thread1) │ │ (thread2) │ │ (thread3) │
56 └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
57 │ │ │
58 ▼ ▼ ▼
59 ┌───────────┐ ┌───────────┐ ┌───────────┐
60 │ ClaudeCli │ │ ClaudeCli │ │ ClaudeCli │
61 │ + MCP srv │ │ + MCP srv │ │ + MCP srv │
62 └───────────┘ └───────────┘ └───────────┘
63```
64 
65**Session contains:**
66- `claude: ClaudeCli` - the Claude CLI process
67- `claudeSessionId: string` - UUID for session persistence/resume
68- `messageManager: MessageManager` - orchestrates all message operations and state
69- `sessionAllowedUsers: Set<string>` - per-session allowlist (includes session owner)
70- `isResumed: boolean` - whether session was resumed after restart
71 
72**MessageManager contains executors that own their state:**
73- `ContentExecutor` - content streaming state
74- `TaskListExecutor` - task list display state
75- `QuestionApprovalExecutor` - pending questions/approvals
76- `PromptExecutor` - context prompts, worktree prompts, update prompts
77- `SubagentExecutor` - active subagent tracking
78- `MessageApprovalExecutor` - unauthorized message approval
79- `BugReportExecutor` - bug report flow
80 
81**MCP Server:**
82- Spawned via `--mcp-config` per Claude CLI instance
83- Each has its own WebSocket/connection to the platform
84- Exposes three tools to Claude:
85 - `permission_prompt` — posts permission requests to the session's thread; returns allow/deny based on user reaction
86 - `send_file` — uploads a file from the session's working directory into the thread (auto-approved; path-validated)
87 - `read_post` — fetches a Mattermost/Slack post (and optional thread context) by permalink, scoped to the bot's own channel (auto-approved)
88 
89## Multi-Platform Support
90 
91**Architecture**: claude-threads supports connecting to multiple chat platforms simultaneously through a platform abstraction layer.
92 
93**Currently Supported**:
94- ✅ Mattermost (fully implemented)
95- ✅ Slack (fully implemented)
96 
97**Key Concepts**:
98 
991. **Platform Abstraction**: `PlatformClient` interface normalizes differences between platforms
1002. **Composite Session IDs**: Sessions are identified by `"platformId:threadId"` to ensure uniqueness across platforms
1013. **Independent Credentials**: Each platform instance has its own URL, token, and channel configuration
1024. **Per-Platform MCP Servers**: Each session's MCP permission server connects to the correct platform
103 
104**Configuration**:
105 
106Multi-platform mode uses YAML config (`~/.config/claude-threads/config.yaml`):
107 
108```yaml
109version: 1
110workingDir: /home/user/repos/myproject
111chrome: false
112worktreeMode: prompt
113respondOnlyWhenMentioned: false # New threads only reply when @mentioned (per-thread !mentions overrides)
114userAttribution: true # Prefix user turns with [@username]: so Claude can tell speakers apart (default on; only applied once a thread has >1 participant)
115 
116# Optional: Customize the sticky channel message
117stickyMessage:
118 description: "Porygon — Mixpanel analytics bot" # Shown below the title
119 footer: "• !stop — End session\n• !help — Show help" # Shown before the default footer
120
121platforms:
122 # Mattermost configuration
123 - id: mattermost-main
124 type: mattermost
125 displayName: Main Team
126 url: https://chat.example.com
127 token: your-bot-token-here
128 channelId: abc123
129 botName: claude-code
130 allowedUsers: [alice, bob]
131 skipPermissions: false
132 
133 # Slack configuration
134 - id: slack-workspace
135 type: slack
136 displayName: Slack Team
137 botToken: xoxb-your-bot-token # Bot User OAuth Token
138 appToken: xapp-your-app-token # App-Level Token (for Socket Mode)
139 channelId: C0123456789
140 botName: claude-bot
141 allowedUsers: [alice, bob] # Slack usernames
142 skipPermissions: false
143```
144 
145**Slack-specific notes:**
146- Requires both a Bot Token (`xoxb-`) and App Token (`xapp-`) for Socket Mode
147- `allowedUsers` uses Slack usernames (not user IDs) for consistency with Mattermost
148- User mentions in messages use Slack user IDs (e.g., `<@U0123ALICE>`) - the bot handles this automatically
149- Bot Token scopes required: `channels:history`, `channels:read`, `chat:write`, `files:read`, `reactions:read`, `reactions:write`, `users:read`
150- App Token scope: `connections:write` (Socket Mode must be enabled in the Slack app)
151 
152Configuration is stored in YAML only - no `.env` file support.
153 
154## Multi-Account Claude Support (opt-in)
155 
156By default every session spawns `claude` with the bot's own `process.env`, so
157they all share one subscription's token budget. When you expect heavy concurrent
158use, configure a pool of accounts in `config.yaml` — new sessions are routed to
159whichever account has the most subscription headroom (see **usage balancing**
160below) and automatically skip accounts that are in rate-limit cooldown.
161 
162```yaml
163# Omit this block entirely → single-account mode (unchanged behavior).
164claudeAccounts:
165 # OAuth Pro/Max — prepare the HOME with `HOME=<path> claude login` first
166 - id: primary
167 home: /home/bot/.claude-accounts/primary
168 - id: backup
169 displayName: Backup (Pro)
170 home: /home/bot/.claude-accounts/backup
171 
172 # API-key billed
173 - id: shared-api
174 apiKey: sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
175```
176 
177How it works:
178 
1791. **Spawn env override.** For `home` we set `HOME` (and `USERPROFILE` on Windows)
180 so Claude reads `.credentials.json`, `.claude/projects/*`, and MCP config from
181 that directory. For `apiKey` we set `ANTHROPIC_API_KEY` (leaving HOME as-is).
1822. **Session → account binding is persisted.** `PersistedSession.claudeAccountId`
183 stores which account a session started on, so resume after a bot restart uses
184 the same credentials — critical for OAuth accounts since the conversation
185 history in `~/.claude/projects/*` lives under that HOME.
1863. **Rate-limit handling.** Claude's stderr and result events are scanned for
187 phrases like `usage limit reached`, `rate_limit_error`, `429 ... rate limit`,
188 `quota exceeded`. On a hit the offending account is cooled down until the
189 extracted reset time (fallback: 1 hour). Future `acquireClaudeAccount()` calls
190 skip cooling accounts; resumed sessions bypass cooldown because their history
191 can't move.
1924. **Usage balancing (new sessions).** Instead of round-robin, the pool routes
193 each new session to the account with the most subscription headroom. At new
194 session start (and only then — there is no background polling) the bot probes
195 every account **on-demand and in parallel** with `claude -p "/usage"
196 --output-format json` under each account's HOME (costs $0, zero turns) and
197 parses the real limit percentages (`Current session` + `Current week`). The
198 load score is `max(session%, week%)`; `acquire()` picks the lowest
199 non-cooling score, tie-breaking by fewest active sessions then config order.
200 Each probe is capped at 10s; an account whose probe fails/times out sorts
201 last (usage "unknown"), so a fresh session is never routed onto a
202 possibly-maxed account before its real usage is known. The sticky channel
203 message shows the pool's `min–max % used` range.
204 
205 > On-demand probing adds ~1–2s to new session start (all accounts are probed
206 > so they can be compared) — negligible next to spawning Claude + the MCP
207 > server, and it keeps routing data always-fresh with zero idle work. Probing
208 > no-ops for pools with fewer than two accounts.
209 >
210 > Usage balancing targets **OAuth (subscription)** accounts — only they
211 > report `/usage` limits. API-key accounts return no percentages; they sort
212 > as "usage unknown" and are picked by the active-session tiebreak. Usage is
213 > cached in memory only for the current pool state; it is re-probed fresh at
214 > each new session start.
215 
216 **Resume is unaffected:** it still passes the persisted `claudeAccountId` as
217 `preferredId`, so a resumed session always re-binds to the account its
218 history lives under, cooling or not.
219 
220Files involved:
221 
222| File | Role |
223|------|------|
224| `src/claude/account-pool.ts` | `AccountPool` — usage-balanced selection, cooldown tracking, active-session accounting. |
225| `src/claude/usage-probe.ts` | Runs `claude -p "/usage"` per account + pure `parseUsageOutput` / `usageLoadScore`. |
226| `src/session/manager.ts` | `refreshAccountUsage()` probes all accounts on-demand at session start; exposed to lifecycle as `ops.refreshClaudeAccountUsage()`. |
227| `src/claude/rate-limit-detector.ts` | Pure parser that turns stderr/JSON into `{ detected, resetAtEpochMs }`. |
228| `src/claude/cli.ts` | `ClaudeCliOptions.account` → overrides `HOME` / `ANTHROPIC_API_KEY` on spawn. Emits `rate-limit` events. |
229| `src/session/lifecycle.ts` | Acquires the account on `startSession`/`resumeSession`, releases on `removeFromRegistry`, handles `rate-limit` events. |
230| `src/operations/commands/handler.ts` | Preserves the account when `!cd` / `!permissions interactive` respawn Claude; adds the 🔑 row to the session header. |
231| `src/operations/sticky-message/handler.ts` | Pool summary in the channel sticky. |
232 
233## Source Files
234 
235### Core
236| File | Purpose |
237|------|---------|
238| `src/index.ts` | Entry point. CLI parsing, bot startup, UI rendering |
239| `src/message-handler.ts` | Message routing logic (extracted for testability) |
240| `src/config.ts` | Type exports for config (re-exports from migration.ts) |
241| `src/config/migration.ts` | YAML config loading (`config.yaml`) |
242| `src/onboarding.ts` | Interactive setup wizard for multi-platform config |
243 
244### Session Management
245 
246Session is a thin container; most logic lives in `src/operations/`:
247 
248| File | Purpose |
249|------|---------|
250| `src/session/manager.ts` | **Orchestrator** - creates sessions, routes messages/reactions |
251| `src/session/reaction-router.ts` | Reaction dispatch: allowlist gate, resume-from-reaction, session-level reactions, MessageManager fallthrough |
252| `src/session/lifecycle.ts` | Session start, resume, exit, cleanup |
253| `src/session/types.ts` | TypeScript types (Session interface) |
254| `src/session/registry.ts` | Session lookup and registration |
255| `src/session/timer-manager.ts` | Per-session timer management |
256| `src/session/index.ts` | Public exports |
257 
258### Operations Layer (The Brain)
259 
260Most business logic lives in `src/operations/`:
261 
262| File | Purpose |
263|------|---------|
264| `src/operations/message-manager.ts` | **The Brain** - orchestrates all operations via executors |
265| `src/operations/transformer.ts` | Transforms Claude events → MessageOperations |
266| `src/operations/types.ts` | Operation types (MessageOperation, TaskItem, etc.) |
267| `src/operations/post-helpers/` | DRY utilities for posting messages (postInfo, postError, etc.) |
268| `src/operations/events/handler.ts` | Claude CLI event handling |
269| `src/operations/commands/handler.ts` | User commands (!cd, !invite, !kick, !permissions) |
270| `src/operations/streaming/handler.ts` | Message batching and flushing to chat |
271| `src/operations/context-prompt/handler.ts` | Thread context prompt for mid-thread session starts |
272| `src/operations/worktree/handler.ts` | Git worktree management |
273| `src/operations/bug-report/handler.ts` | Bug report flow |
274| `src/operations/sticky-message/handler.ts` | Channel sticky message |
275| `src/operations/tool-formatters/` | Format tool use for display |
276 
277### Executors (State Owners)
278 
279Each executor owns a specific piece of interactive state:
280 
281| File | Purpose |
282|------|---------|
283| `src/operations/executors/content.ts` | Content streaming state |
284| `src/operations/executors/task-list.ts` | Task list display |
285| `src/operations/executors/question-approval.ts` | Questions and plan approval |
286| `src/operations/executors/prompt.ts` | Context prompts, worktree prompts, update prompts |
287| `src/operations/executors/subagent.ts` | Subagent tracking |
288| `src/operations/executors/message-approval.ts` | Unauthorized message approval |
289| `src/operations/executors/bug-report.ts` | Bug report flow |
290| `src/operations/executors/system.ts` | System messages |
291| `src/operations/executors/worktree-prompt.ts` | Worktree prompts |
292 
293**Design Pattern**: MessageManager delegates to executors. Each executor owns its state and handles its reactions. This keeps Session minimal while centralizing all message operations.
294 
295### Claude CLI
296| File | Purpose |
297|------|---------|
298| `src/claude/cli.ts` | Spawns Claude CLI with platform-specific MCP config |
299| `src/claude/types.ts` | TypeScript types for Claude stream-json events |
300| `src/claude/version-check.ts` | Claude CLI version validation and compatibility check |
301 
302### Platform Layer
303| File | Purpose |
304|------|---------|
305| `src/platform/client.ts` | PlatformClient interface (abstraction for all platforms) |
306| `src/platform/types.ts` | Normalized types (PlatformPost, PlatformUser, PlatformReaction, etc.) |
307| `src/platform/formatter.ts` | PlatformFormatter interface (markdown dialects) |
308| `src/platform/utils.ts` | **NEW** - Platform-agnostic utilities (message splitting, emoji normalization, retry logic) |
309| `src/platform/IMPLEMENTATION_GUIDE.md` | **NEW** - Guide for implementing new platform support |
310| `src/platform/index.ts` | Public exports |
311| `src/platform/mattermost/client.ts` | Mattermost implementation of PlatformClient |
312| `src/platform/mattermost/types.ts` | Mattermost-specific types |
313| `src/platform/mattermost/formatter.ts` | Mattermost markdown formatter |
314| `src/platform/mattermost/permalink.ts` | Mattermost permalink parser + resolver + formatter for `read_post` |
315| `src/platform/slack/client.ts` | Slack implementation of PlatformClient (Socket Mode + Web API) |
316| `src/platform/slack/types.ts` | Slack-specific types |
317| `src/platform/slack/formatter.ts` | Slack mrkdwn formatter |
318| `src/platform/slack/mcp-platform-api.ts` | Slack MCP platform API (used by MCP child) |
319| `src/platform/slack/permalink.ts` | Slack permalink parser + resolver + formatter for `read_post` |
320| `src/platform/slack/index.ts` | Slack module exports |
321| `src/platform/permalink-shared.ts` | Cross-platform permalink utilities (caps, truncation, quote-block) shared by both permalink modules |
322| `src/platform/test-helpers/fetch-harness.ts` | Shared `fetch` recorder + responder for platform-API unit tests |
323 
324### Utilities
325| File | Purpose |
326|------|---------|
327| `src/utils/emoji.ts` | Emoji constants and validators (platform-agnostic) |
328| `src/utils/logger.ts` | Component-based logging with session context |
329| `src/utils/session-log.ts` | Session-aware logging utilities |
330| `src/utils/format.ts` | ID formatting, time/duration formatting |
331| `src/utils/colors.ts` | Terminal color utilities |
332| `src/utils/keep-alive.ts` | Prevent system sleep during sessions |
333| `src/utils/battery.ts` | Battery status monitoring |
334| `src/utils/uptime.ts` | Session uptime tracking |
335| `src/utils/pr-detector.ts` | Detect PR URLs in Claude output |
336| `src/mcp/mcp-server.ts` | MCP server: permission prompts, send_file, read_post (platform-agnostic) |
337| `src/platform/mcp-platform-api-factory.ts` | Factory for platform-specific MCP platform APIs |
338| `src/platform/mcp-platform-api.ts` | McpPlatformApi interface |
339| `src/mattermost/api.ts` | Standalone Mattermost API helpers |
340| `src/persistence/session-store.ts` | Multi-platform session persistence |
341| `src/ui/components/Header.tsx` | Terminal header with the ASCII logo |
342 
343## How the Permission System Works
344 
3451. **Claude CLI is started with:**
346```
347 claude --input-format stream-json --output-format stream-json --verbose \
348 --mcp-config '{"mcpServers":{"claude-threads-mcp":{...}}}' \
349 --permission-prompt-tool mcp__claude-threads-mcp__permission_prompt
350```
351 
3522. **When Claude needs permission** (e.g., to write a file), it calls the MCP tool
353 
3543. **The MCP server** (running as a subprocess):
355 - Receives the permission request via stdio
356 - Posts a message to the chat thread: "⚠️ Permission requested: Write `file.txt`"
357 - Adds reaction options (👍 ✅ 👎) to the message
358 - Opens a WebSocket to the platform and waits for a reaction
359 
3604. **User reacts** with an emoji
361 
3625. **MCP server**:
363 - Validates the user is in ALLOWED_USERS
364 - Ignores bot's own reactions (the reaction options)
365 - Returns `{behavior: "allow"}` or `{behavior: "deny"}` to Claude CLI
366 
3676. **Claude CLI** proceeds or aborts based on the response
368 
369## Configuration
370 
371Configuration is stored in YAML at `~/.config/claude-threads/config.yaml`.
372 
373**First run:** If no config exists, interactive onboarding guides you through setup.
374 
375### Environment Variables (Optional)
376 
377| Variable | Description |
378|----------|-------------|
379| `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
380| `SESSION_TIMEOUT_MS` | Idle session timeout in ms (default: `1800000` = 30 min) |
381| `DEBUG` | Set `1` for debug logging |
382| `CLAUDE_PATH` | Custom path to claude binary (default: `claude`) |
383| `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | Set `1` to have Claude strip Anthropic/cloud credentials (`ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_BEARER_TOKEN_BEDROCK`, `GOOGLE_APPLICATION_CREDENTIALS`) from Bash, hook, and stdio-MCP subprocesses it spawns. Bot env vars like `PLATFORM_TOKEN` / `MATTERMOST_TOKEN` / `SLACK_BOT_TOKEN` pass through untouched — verified empirically against CLI 2.1.116. **Side effect:** setting this also forces permission mode to `default` — Claude will refuse `--dangerously-skip-permissions` and log a warning. Only enable if all your sessions run with interactive permissions. Requires Claude CLI 2.1.83+. |
384 
385The bot also sets two Claude CLI tuning flags by default on the child process,
386and only if you haven't already set them in the parent env:
387 
388- `MCP_CONNECTION_NONBLOCKING=true` — caps `--mcp-config` server connects at 5s
389 so a slow MCP server never blocks session start (Claude CLI 2.1.89+).
390- `ENABLE_PROMPT_CACHING_1H=true` — opts into the 1-hour prompt cache TTL,
391 reducing re-caching cost on long-lived threads (Claude CLI 2.1.108+).
392 
393Export either with a non-default value (e.g. `MCP_CONNECTION_NONBLOCKING=false`)
394to disable.
395 
396### Lockfiles: why there are two, and the trap
397 
398The repo carries **both** `bun.lock` and `package-lock.json`, and they do
399different jobs:
400 
401| File | Role |
402|------|------|
403| `bun.lock` | What CI actually installs from. Every workflow runs `bun install`. This is the source of truth for what gets tested, built and published. |
404| `package-lock.json` | Installed by nothing. It exists so Dependabot **security** updates keep working: Dependabot supports bun for *version* updates only, so with bun.lock alone you lose security-advisory PRs. Also a second Trivy scan surface. |
405 
406**The trap:** Dependabot only ever rewrites `package-lock.json`. A dependency
407bump therefore lands, goes green, and never reaches CI — because the version CI
408installs still comes from `bun.lock`. This is not hypothetical; #434 and #442
409both needed `bun.lock` regenerated by hand before the bump took effect.
410`.github/workflows/dependabot-sync-lockfile.yml` now does that automatically on
411Dependabot's PRs.
412 
413Note that the two lockfiles **cannot be kept fully identical** — npm and bun hoist
414transitive trees differently, and both results are valid, so dozens of
415transitive versions legitimately differ. Only *direct* dependencies resolving
416differently indicates a real problem. Don't try to enforce byte-level parity.
417 
418**Longer-term option:** [Renovate](https://docs.renovatebot.com/modules/manager/bun/)
419supports bun natively, including `vulnerabilityAlerts` for security-driven PRs.
420Switching to it would let the repo drop `package-lock.json` entirely and remove
421this whole class of drift. That is a deliberate change of dependency bot, not
422something to do incidentally.
423 
424### Runtime Version Policy
425 
426**Floor**: Node 20 (in maintenance LTS through April 2026), Bun 1.2.21.
427Both are declared in `package.json#engines`.
428 
429**Why Node 20 and not higher**: no production dep requires more, and bumping
430the floor strands users on otherwise-supported LTS lines. Forced to 20 by
431`@hono/node-server@2` in v1.8.2.
432 
433**CI strategy**:
434- Bun is pinned (`BUN_VERSION` env in every workflow) so a Bun release can't
435 silently break us. Bump in lockstep when upgrading.
436- `publish.yml` builds under Node 20 (the floor) so any unsafe API call that
437 only exists on newer Node breaks the build before reaching users.
438- `ci.yml` has a `node-smoke` matrix (`[20, 22, 24]`) that runs the built
439 binary under each currently-relevant Node line.
440 
441**When to bump the floor**: only when a real dep forces it. Update
442`package.json#engines.node`, `publish.yml` Node version, the `node-smoke`
443matrix floor, the README prereqs line, and call it out as breaking in the
444CHANGELOG.
445 
446### Claude CLI Version Requirements
447 
448claude-threads requires a compatible version of the Claude CLI (`@anthropic-ai/claude-code`).
449 
450**Compatible versions:** `>=2.0.74 <2.2.0` (covers the full 2.1.x line; latest verified: 2.1.116)
451 
452The version is checked at startup. If an incompatible version is detected:
453- The bot will display an error message and exit
454- Use `--skip-version-check` to bypass (not recommended)
455 
456To install the latest verified compatible version:
457```bash
458npm install -g @anthropic-ai/claude-code@2.1.116
459```
460 
461The Claude CLI version is displayed:
462- At bot startup in the terminal
463- In the sticky channel message status bar
464- In each session's header table
465 
466**Updating the version range:** Edit `CLAUDE_CLI_VERSION_RANGE` in `src/claude/version-check.ts` when testing with new Claude CLI versions.
467 
468## Development Commands
469 
470```bash
471bun install # Install dependencies
472bun run build # Compile TypeScript to dist/
473bun run dev # Run from source with watch mode
474bun start # Run compiled version
475bun test # Run unit tests (~2600 tests)
476bun run lint # Run ESLint
477```
478 
479### Integration Tests
480 
481Integration tests run the actual bot against a real Mattermost instance with a mock Claude CLI.
482 
483```bash
484# Run locally (requires Docker)
485bun run test:integration:setup # Start Mattermost in Docker + create users/channels
486bun run test:integration:run # Run ~120 integration tests
487bun run test:integration:teardown # Stop Mattermost
488 
489# Or run all at once (CI style)
490bun run test:integration
491```
492 
493**What's tested:**
494- Session lifecycle (start, response, end, timeout)
495- Commands (!stop, !escape, !help, !cd, !kill, !permissions)
496- Reaction-based controls (❌ cancel, ⏸️ interrupt)
497- Multi-user collaboration (!invite, !kick, message approval)
498- Session persistence and resume
499- Plan approval, question flows, context prompts
500- Git worktree integration
501- Error handling, MAX_SESSIONS limits
502 
503**CI:** Integration tests run automatically on PRs via `.github/workflows/integration.yml`
504 
505## Testing Locally
506 
5071. Create config: `~/.config/claude-threads/config.yaml` (or run `claude-threads` for interactive setup)
5082. Build: `bun run build`
5093. Run: `bun start` (or `DEBUG=1 bun start` for verbose output)
5104. In Mattermost, @mention the bot: `@botname write "hello" to test.txt`
5115. Watch the permission prompt appear, react with 👍
5126. Verify file was created
513 
514## Publishing a New Version
515 
516Releases are automated via GitHub Actions. There are two paths, and both end in an
517npm publish:
518 
5191. **Version bump lands on `main` → `.github/workflows/release.yml`** verifies the
520 tree, creates the tag, creates the GitHub release, and publishes. This needs no
521 local machine and no `gh` CLI — merging the version-bump commit is enough.
5222. **You create a release by hand with `gh release create`** → the existing
523 `.github/workflows/publish.yml` fires on `release: published` and publishes.
524 
525### Automated Release Flow (no local machine needed)
526 
527Everything here can be done from the GitHub web UI or by an agent that can only
528push branches and merge PRs:
529 
530```
5311. Open a PR that:
532 a. Bumps the version in package.json with the semver level the change
533 warrants — fixes only → `npm version patch`, a new feature → `npm version
534 minor`, a breaking change → `npm version major` (all with
535 --no-git-tag-version). `npm version` also updates package-lock.json; run
536 `bun install` afterwards so bun.lock stays in lockstep (bun.lock does not
537 record the root version, so it is usually a no-op — see the lockfile note
538 above).
539 b. Promotes the CHANGELOG's `## [Unreleased]` heading to
540 `## [X.Y.Z] - YYYY-MM-DD` (the new version and today's date), so the
541 release notes match the tag.
5422. Merge it to main once CI is green.
5433. release.yml picks up the package.json change, re-runs typecheck/lint/knip/
544 tests/build, then tags, releases and publishes.
545```
546 
547The bump must land as a **new** version: release.yml exits without publishing if
548the current version's tag already exists, so re-using a version that was already
549released (e.g. leaving package.json unchanged) is a silent no-op, not a release.
550 
551`release.yml` is safe to re-run and safe against unrelated `package.json` edits:
552if the tag for the current version already exists it exits before tagging or
553publishing. It also exposes `workflow_dispatch`, so a release can be kicked off
554from the Actions tab (or the API) if the push trigger was missed — for example
555when the version bump reached `main` before the workflow existed.
556 
557> **Why `release.yml` publishes directly instead of handing off to `publish.yml`:**
558> a release created with `GITHUB_TOKEN` does not emit a `release: published` event
559> that can start another workflow — GitHub suppresses those to avoid recursive
560> runs. A tag-and-release-only job would therefore create the release and then
561> never publish. The alternative is a long-lived PAT; publishing in-job avoids
562> that credential entirely.
563 
564### Quick Release Flow (with open PRs)
565 
566When there are open PRs to merge before releasing:
567 
568```bash
569# 1. List open PRs and verify checks pass before merging
570gh pr list --state open
571gh pr checks &lt;PR_NUMBER&gt; # Ensure all checks pass!
572 
573# 2. Merge PRs (squash merge, delete branches)
574gh pr merge &lt;PR_NUMBER&gt; --squash --delete-branch
575# Repeat for each PR (ignore worktree branch deletion errors)
576 
577# 3. Pull merged changes
578git pull
579 
580# 4. Remove any deprecated files if needed
581rm &lt;file&gt; && git add -A
582 
583# 5. Update CHANGELOG.md with new version and all merged PR changes
584# Use format: **Feature/Fix name** - Description (#PR_NUMBER)
585 
586# 6. Commit changelog
587git add CHANGELOG.md && git commit -m &quot;Update CHANGELOG for vX.Y.Z&quot;
588 
589# 7. Bump version
590npm version patch --no-git-tag-version # 0.47.0 → 0.47.1 (fixes only)
591npm version minor --no-git-tag-version # 0.47.0 → 0.48.0 (new features)
592npm version major --no-git-tag-version # 0.47.0 → 1.0.0 (breaking changes)
593 
594# 8. Commit and tag
595git add package.json package-lock.json && git commit -m &quot;X.Y.Z&quot; && git tag vX.Y.Z
596 
597# 9. Push to GitHub with tags
598git push && git push --tags
599 
600# 10. Create GitHub release (triggers automatic npm publish)
601gh release create vX.Y.Z --title &quot;vX.Y.Z&quot; --generate-notes
602```
603 
604### Manual Release Flow (no PRs to merge)
605 
606**IMPORTANT: Always test locally before pushing!**
607```bash
608# 0. Build and run locally to test
609bun run build && bun start
610# Test in Mattermost: https://digilab.overheid.nl/chat/digilab/channels/annes-claude-code-sessies
611# Kill the server when done testing (Ctrl+C)
612```
613 
614```bash
615# 1. Update CHANGELOG.md with the new version
616 
617# 2. Commit the changelog
618git add CHANGELOG.md && git commit -m &quot;Update CHANGELOG for vX.Y.Z&quot;
619 
620# 3. Bump version
621npm version patch --no-git-tag-version # then commit and tag manually
622 
623# 4. Commit and tag
624git add package.json package-lock.json && git commit -m &quot;X.Y.Z&quot; && git tag vX.Y.Z
625 
626# 5. Push to GitHub with tags
627git push && git push --tags
628 
629# 6. Create GitHub release (this triggers automatic npm publish)
630gh release create vX.Y.Z --title &quot;vX.Y.Z&quot; --generate-notes
631```
632 
633**GitHub Actions Workflow:** `.github/workflows/publish.yml`
634- Triggered on: GitHub release published
635- Builds TypeScript and publishes to the npm registry
636- Requires `NPM_TOKEN` secret in repository settings
637 
638**⚠️ IMPORTANT: NEVER modify `publish.yml`'s trigger!**
639- The workflow MUST trigger on `release: types: [published]`
640- NEVER change it to trigger on tag pushes
641- This is the path for a release a human creates via `gh release create`
642- This is the preferred manual release workflow - do not change it
643- Automating a release is done in `release.yml` instead, which is a *separate*
644 workflow — never by retargeting this one
645 
646**Both workflows publish, but they cannot double-publish silently:** `release.yml`
647skips entirely when the current version's tag already exists, and npm refuses to
648republish a version that is already on the registry, so a redundant run fails
649loudly rather than shipping anything unexpected.
650 
651**Token Setup (already configured):**
652- Classic Automation token stored in GitHub repository secrets as `NPM_TOKEN`
653- To update: https://github.com/anneschuth/claude-threads/settings/secrets/actions
654 
655## Testing Deployed Versions in Mattermost
656 
657After deploying a new version, test it in the Mattermost channel:
658https://digilab.overheid.nl/chat/digilab/channels/annes-claude-code-sessies
659 
660### Basic Verification
6611. **Check version**: `@minion-of-anne what version are you running?`
662 - Bot should respond with version number and summary of recent changes
663 - Verify the session header shows correct version
664 
665### Testing Permission System
6661. **Start a new session** (existing sessions keep their original permission mode)
6672. **Enable interactive permissions**: `!permissions interactive`
668 - Should see: "🔐 **Interactive permissions enabled** ... *Claude Code restarted with permission prompts*"
669 - Session header should update to show "Permissions: Interactive"
6703. **Test permission prompt**: `@minion-of-anne write "test" to /tmp/perm-test.txt`
671 - Should see a permission prompt with reaction options: 👍 ✅ 👎
672 - React with 👍 to approve
673 - File should be written after approval
674 
675### Testing Other Features
676- **Session collaboration**: `!invite @username` / `!kick @username`
677- **Directory change**: `!cd /some/path` (restarts Claude CLI)
678- **Interrupt**: `!escape` or ⏸️ reaction (interrupts without killing)
679- **Cancel**: `!stop` or ❌/🛑 reaction (kills the session)
680- **Plan approval**: When Claude presents a plan, react with 👍/👎
681- **Question answering**: When Claude asks questions, react with number emojis
682 
683### Verifying Specific Bug Fixes
684When testing a specific fix:
6851. Reproduce the original bug scenario
6862. Verify the fix works as expected
6873. Check for regressions in related functionality
688 
689## Data Retention & Security
690 
691claude-threads stores sensitive session data locally. The following retention policies and security measures apply:
692 
693### Data Storage Locations
694 
695| Data | Location | Retention | Permissions |
696|------|----------|-----------|-------------|
697| Session state | `~/.config/claude-threads/sessions.json` | Active + 3 days after soft-delete | `0600` (owner only) |
698| Thread logs | `~/.claude-threads/logs/{platformId}/` | 30 days (configurable) | `0600` (owner only) |
699| Worktree metadata | `~/.claude-threads/worktrees.json` | Until worktree cleanup | `0600` (owner only) |
700| Configuration | `~/.config/claude-threads/config.yaml` | Permanent | `0600` (owner only) |
701 
702### Automatic Cleanup
703 
704- **Session purge**: Inactive sessions are soft-deleted after session timeout, then permanently removed after 3 days
705- **Thread logs**: Automatically deleted after 30 days (configurable via `threadLogs.retentionDays` in config)
706- **Worktrees**: Orphaned worktrees (no active session, >24h old) are cleaned up automatically
707- **Cleanup scheduler**: Runs hourly in the background
708 
709### Security Measures
710 
711- All sensitive files use restrictive permissions (`0600` - owner read/write only)
712- Session tokens and credentials are never written to disk (config file excluded)
713- Permission decisions are logged for audit purposes
714- Bot tokens should be stored securely (environment variables or secure config)
715 
716## Common Issues & Solutions
717 
718### "Permission server not responding"
719- Check that `MATTERMOST_URL` and `MATTERMOST_TOKEN` are passed to MCP server
720- Look for `[MCP]` prefixed logs in stderr
721- Enable `DEBUG=1` for verbose MCP logging
722 
723### "Reaction not detected"
724- The MCP server has its own WebSocket connection (separate from main bot)
725- Check that the reacting user is in `ALLOWED_USERS`
726- Bot's own reactions (adding the 👍 ✅ 👎 options) are filtered out
727 
728### "Claude CLI not found"
729- Ensure `claude` is in PATH, or set `CLAUDE_PATH` environment variable
730- The CLI must support `--permission-prompt-tool` (recent versions)
731 
732### "MCP config schema error"
733- The config must be wrapped: `{"mcpServers": {"name": {"type": "stdio", ...}}}`
734- Check `src/claude/cli.ts` for the exact format
735 
736### "TypeScript build errors"
737- Run `bun install` to ensure dependencies are up to date
738- Check for type mismatches in event handling
739 
740## Debugging with Claude Code History
741 
742Claude Code stores all conversation history on disk, which is invaluable for debugging:
743 
744```
745~/.claude/
746├── history.jsonl # Index of all sessions (metadata only)
747├── projects/ # Full conversation transcripts
748│ └── -Users-username-project/ # Encoded path (/ → -)
749│ ├── session-id-1.jsonl # Full conversation
750│ └── session-id-2.jsonl
751├── todos/ # Todo lists per session
752└── settings.json # User settings
753```
754 
755**Useful debugging commands:**
756```bash
757# List recent sessions
758tail -20 ~/.claude/history.jsonl | jq -r '.cwd + " " + .name'
759 
760# Find sessions for this project
761ls ~/.claude/projects/-Users-anneschuth-mattermost-claude-code/
762 
763# View a specific session's conversation
764cat ~/.claude/projects/-Users-anneschuth-mattermost-claude-code/SESSION_ID.jsonl | jq .
765```
766 
767**Key points:**
768- Directory names are encoded: `/path/to/project/` → `-path-to-project`
769- Each session gets a JSONL file with full conversation history
770- Consider backing up `~/.claude/` regularly
771 
772## Key Implementation Details
773 
774### Event Flow (src/operations/transformer.ts → MessageManager)
775Claude CLI emits JSON events. The transformer converts them to MessageOperations:
776- `assistant` → `AppendContentOp` (text response)
777- `tool_use` → `AppendContentOp` (tool display) or special ops (TaskListOp, QuestionOp, etc.)
778- `tool_result` → `AppendContentOp` (result indicator) + `FlushOp`
779- `result` → `FlushOp` + `StatusUpdateOp` (cost info)
780 
781### Message Streaming (src/operations/streaming/handler.ts)
782- Messages are batched and flushed periodically via `FlushOp`
783- Long content is split across multiple posts (16K limit)
784- Diffs and code blocks use syntax highlighting
785 
786### Reaction Handling (MessageManager → Executors)
787- `MessageManager.handleReaction()` routes to appropriate executor
788- Each executor handles its own pending state (questions, approvals, prompts)
789- MCP server handles: permission prompts (separate from main bot)
790- All filter to only process allowed users' reactions
791 
792## Backward Compatibility Requirements
793 
794**CRITICAL:** When modifying persisted data structures (`PersistedSession`, `config.yaml`, `sessions.json`), you MUST maintain backward compatibility. Users may upgrade from any older version, and their persisted data must continue to work.
795 
796### Rules for Persisted Data Changes
797 
7981. **Never remove fields** - Old data may have them, and removing causes silent failures
7992. **Never rename fields without migration** - Add migration logic in `session-store.ts` to convert old field names
8003. **Always use defensive defaults** - When reading persisted data, use `??` or `||` to provide fallbacks:
801```typescript
802 // GOOD - handles missing fields gracefully
803 sessionNumber: state.sessionNumber ?? 1,
804 sessionAllowedUsers: new Set(state.sessionAllowedUsers || [state.startedBy].filter(Boolean)),
805 
806 // BAD - crashes if field is missing
807 sessionNumber: state.sessionNumber,
808 sessionAllowedUsers: new Set(state.sessionAllowedUsers),
809```
8104. **Check both old and new field names** when looking up data:
811```typescript
812 // GOOD - supports both old and new field names
813 const lifecycleId = session.lifecyclePostId || (session as LegacySession).timeoutPostId;
814```
8155. **Add migrations for field renames** - See `session-store.ts` for examples of migrating `timeoutPostId` → `lifecyclePostId`
816 
817### Testing Backward Compatibility
818 
819When making changes to persisted data:
8201. Create a test session with the OLD code
8212. Upgrade to the NEW code
8223. Verify the session resumes correctly
8234. Verify all features work (tasks, permissions, worktrees, etc.)
824 
825### Red-Green Testing for Regression Fixes
826 
827> **CRITICAL: Always verify your test is RED without the fix!**
828>
829> A test that passes regardless of whether the fix exists is USELESS.
830> It won't catch future regressions. This is the most common testing mistake.
831 
832**The RED-GREEN-REFACTOR cycle:**
833 
834| Step | Action | Verification |
835|------|--------|--------------|
836| 1. **RED** | Write test, temporarily remove fix | Test FAILS |
837| 2. **GREEN** | Apply the fix | Test PASSES |
838| 3. **REFACTOR** | Clean up code | All tests still pass |
839 
840**The #1 Rule:**
841```
842TEST THE ACTUAL CODE PATH, NOT A COPY OF THE LOGIC!
843```
844 
845- **WRONG**: Test duplicates the if/then logic inline in the test
846 - Test passes even if someone deletes the fix
847 - Useless for catching regressions
848 
849- **RIGHT**: Test calls the actual function that contains the fix
850 - Test fails if fix is removed
851 - Actually protects against regressions
852 
853**How to verify your test is RED:**
854```bash
855# 1. Temporarily comment out or revert the fix
856git diff src/path/to/file.ts # Note what you're removing
857 
858# 2. Run JUST your test - it MUST fail
859bun test --test-name-pattern &quot;your test name&quot;
860 
861# 3. If test passes → YOUR TEST IS BROKEN, rewrite it!
862# If test fails → Good! Now restore the fix.
863 
864# 4. Run test again with fix - should pass
865bun test --test-name-pattern &quot;your test name&quot;
866 
867# 5. Run all tests
868bun test
869```
870 
871**Key principles:**
872- Test the specific function/method that contains the fix
873- If code is hard to test, refactor for testability FIRST
874- A regression test MUST fail if someone removes the fix
875- "Documents behavior" is NOT the same as "tests the code"
876 
877### Files That Store Persisted Data
878 
879- `~/.config/claude-threads/sessions.json` - Session state (`PersistedSession` interface)
880- `~/.config/claude-threads/config.yaml` - Bot configuration
881 
882## Future Improvements to Consider
883 
884- [x] Implement Slack platform support - **Done**
885- [ ] Add rate limiting for API calls
886- [x] Support file uploads via chat attachments - **Done**
887- [x] Support multiple concurrent sessions (different threads) - **Done in v0.3.0**
888- [x] Add `!stop` command to abort running session - **Done in v0.3.4** (also ❌/🛑 reactions)
889- [x] CLI arguments and interactive onboarding - **Done in v0.4.0**
890- [x] Session collaboration (`!invite`, `!kick`, message approval) - **Done in v0.5.0**
891- [x] Persist session state for recovery after restart - **Done in v0.9.0**
892- [x] Add `!escape` command to interrupt without killing session - **Done in v0.10.0** (also ⏸️ reaction)
893- [x] Add `!kill` command to emergency shutdown all sessions and exit - **Done in v0.10.0**
894- [x] Multi-platform architecture - **Done in v0.14.0**
895- [x] Modular session management - **Done in v0.14.0**
896- [ ] Add rate limiting for API calls
897- [ ] Support file uploads via Mattermost attachments
898- [ ] Keep task list at the bottommost message (always update to latest position)
899- [ ] Session restart improvements: verify all important state is preserved (cwd ✓, permissions ✓, worktree ✓)
900- [x] Accurate context usage: Use per-request `usage` field from result events instead of cumulative billing tokens - **Done**
901 

Commands it names

  • npm install -g @anthropic-ai/claude-code@2.1.116
  • bun install
  • bun run build
  • bun run dev
  • bun start
  • bun test
  • bun run lint
  • bun run test:integration:setup
  • bun run test:integration:run
  • bun run test:integration:teardown
  • bun run test:integration
  • gh pr list --state open
  • gh pr checks <PR_NUMBER>
  • gh pr merge <PR_NUMBER> --squash --delete-branch
  • git pull
  • git add CHANGELOG.md && git commit -m "Update CHANGELOG for vX.Y.Z"
  • npm version patch --no-git-tag-version
  • npm version minor --no-git-tag-version
  • npm version major --no-git-tag-version
  • git add package.json package-lock.json && git commit -m "X.Y.Z" && git tag vX.Y.Z
  • git push && git push --tags
  • gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
  • bun run build && bun start
  • git diff src/path/to/file.ts
  • bun test --test-name-pattern "your test name"
  • bun.lock
  • node-smoke
  • gh release create
  • npm version patch
  • npm version

Sections

  • Claude Code Instructions for claude-threads
  • What This Project Does
  • Contribution Conventions
  • Architecture Overview
  • Multi-Platform Support
  • Optional: Customize the sticky channel message
  • Multi-Account Claude Support (opt-in)
  • Omit this block entirely → single-account mode (unchanged behavior).
  • Source Files
  • Core
  • Session Management
  • Operations Layer (The Brain)
  • Executors (State Owners)
  • Claude CLI
  • Platform Layer
  • Utilities
  • How the Permission System Works
  • Configuration
  • Environment Variables (Optional)
  • Lockfiles: why there are two, and the trap
  • Runtime Version Policy
  • Claude CLI Version Requirements
  • Development Commands
  • Integration Tests
  • Run locally (requires Docker)
  • Or run all at once (CI style)
  • Testing Locally
  • Publishing a New Version
  • Automated Release Flow (no local machine needed)
  • Quick Release Flow (with open PRs)
  • 1. List open PRs and verify checks pass before merging
  • 2. Merge PRs (squash merge, delete branches)
  • Repeat for each PR (ignore worktree branch deletion errors)
  • 3. Pull merged changes
  • 4. Remove any deprecated files if needed
  • 5. Update CHANGELOG.md with new version and all merged PR changes
  • Use format: **Feature/Fix name** - Description (#PR_NUMBER)
  • 6. Commit changelog
  • 7. Bump version
  • 8. Commit and tag
  • 9. Push to GitHub with tags
  • 10. Create GitHub release (triggers automatic npm publish)
  • Manual Release Flow (no PRs to merge)
  • 0. Build and run locally to test
  • Test in Mattermost: https://digilab.overheid.nl/chat/digilab/channels/annes-claude-code-sessies
  • Kill the server when done testing (Ctrl+C)
  • 1. Update CHANGELOG.md with the new version
  • 2. Commit the changelog
  • 3. Bump version
  • 4. Commit and tag
  • 5. Push to GitHub with tags
  • 6. Create GitHub release (this triggers automatic npm publish)
  • Testing Deployed Versions in Mattermost
  • Basic Verification
  • Testing Permission System
  • Testing Other Features
  • Verifying Specific Bug Fixes
  • Data Retention & Security
  • Data Storage Locations
  • Automatic Cleanup

What it covers

setupbuildtestlint-formatcode-stylearchitecturetesting-strategygit-prsecuritydeploymentagent-behaviourdocs

Stack — with the evidence

typescript

(1.00)

bun

(1.00)

eslint

(1.00)

node

(0.95)

react

(0.70)

hono

(0.70)

javascript

(0.60)

github-actions

(0.60)

Format

CLAUDE.md

Claude Code's memory file. Shaped like AGENTS.md but with two things it lacks: @path imports, so shared rules live in one place, and a user-scope layer that follows the developer across repos rather than shipping with the code.

What the corpus says about it

Repository

Owner
anneschuth
Language
—
License
—
Archived
no

All configs in this repo

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.4kCLAUDE.mdtypescriptnode+16setupbuildstylearch+2100/1003 days ago
Adit-Jain-srm/NightmareNetCLAUDE.md · 45CLAUDE.mdtypescriptpython+18buildtestlint-formatstyle+6100/1003 days ago
microsoft/playwrightCLAUDE.md · 94kCLAUDE.mdtypescriptjavascript+10buildtestlint-formatstyle+7100/1003 days ago
dotCMS/corecore-web/CLAUDE.md · 949CLAUDE.mdjavanode+13teststylearchtesting-strategy+3100/1003 days ago
dotCMS/coreCLAUDE.md · 949CLAUDE.mdjavanode+9setupbuildteststyle+799/100today
lollipopkit/flutter_server_boxCLAUDE.md · 8.3kCLAUDE.mddartflutter+8buildteststylearch+298/1003 days ago
carrot-foundation/middle-earthCLAUDE.md · 0CLAUDE.mdtypescriptnode+12setupbuildtestlint-format+697/1003 days ago
caliber-ai-org/ai-setupCLAUDE.md · 1.2kCLAUDE.mdtypescriptnode+5buildtestlint-formatstyle+197/1003 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack