agentleFS
dotBunnydotBunny/Claudette/CLAUDE.md

Claudette

CLAUDE.md7 starsChanged 14 days agoMITView on GitHub

A .NET agentic development environment (ADE) for the desktop, formerly Claudette: today it wraps Claude Code, its first agent, in a native GUI, one tab per Claude Code session, with live usage-limit tracking. Its mascot on the composer is Joulene. DESIGN.md is the spec. §n always means a section of DESIGN.md. GUIDE.md is the plan for the user guide in docs/: how its pages are written and their pictures taken, and which page describes each part of Deventry. CHANGELOG.md says…

  • Reads credentials

The file

Show its rules and text

We split this CLAUDE.md into the rules it gives an agent

22 rules. About 0 are common, 9 are distinctive, and 13 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 13

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    Read the relevant DESIGN.md sections before building a feature.

    line 13
  2. Distinctive:

    If the implementation has to differ from the design, update DESIGN.md in the same change.

    line 14
  3. Very specific:

    Use the app's own color tokens from App.axaml (MutedTextBrush, DividerBrush and so on), not Fluent's internal resource names.

    line 55
  4. Very specific:

    Put text in another font or size beside other text (monospace beside the interface font) on its baseline with …

    line 61
  5. Very specific:

    Start processes only through IProcessLauncher.

    line 67
  6. Very specific:

    Use the injected TimeProvider for anything time-based.

    line 68
  7. Distinctive:

    Check and record in one locked step, never two.

    line 71
  8. Distinctive:

    Don't start a timer or callback while holding a lock it takes: a clock can run it at once, on the same thread.

    line 73
  9. Very specific:

    Never hard-code paths to the app data folder, the session library or ~/.claude.

    line 77
  10. Very specific:

    Prefer documented behavior.

    line 84
  11. Very specific:

    Start the other programs Deventry runs for the user (git, p4, diff tools) through UserEnvironment.ApplyAsync (or Apply), so they get the …

    line 86
  12. Very specific:

    Read Claude Code's fields in Core: give a message or system subtype a typed view (as SystemMessage.HookRun) rather than reading Raw in the …

    line 87
  13. Distinctive:

    Never let one bad line end a session.

    line 93
  14. Distinctive:

    Use them as a working reference for the wire protocol, and to check how a new Claude Code version behaves.

    line 104
  15. Very specific:

    never read or write the real ~/.claude,

    line 116
  16. Very specific:

    Never use a real Perforce server or real Perforce credentials: use FakeP4 (tests/Deventry.Core.Tests/Support/).

    line 119
  17. Distinctive:

    Wait for the state the next step reads, not one that comes before it, stands in for it, or is still true from the step before.

    line 122
  18. Distinctive:

    Write each test for all three systems.

    line 130
  19. Very specific:

    Run tests/Deventry.App.UiTests before pushing a change to a view, a style or a color token: a snapshot left stale fails CI on every system.

    line 138
  20. Very specific:

    When CI is red or a test is flaky, the ci-failures skill (.claude/skills/ci-failures/SKILL.md) finds the cause: collect the failures, tell …

    line 139
  21. Very specific:

    If ~/.dotnet (DOTNET_ROOT) has it, use that dotnet by putting the folder first on PATH.

    line 157
  22. Distinctive:

    Run the app without using tokens by pointing it at the mock API:

    line 160

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

Deventry

A .NET agentic development environment (ADE) for the desktop, formerly Claudette: today it wraps Claude Code, its first agent, in a native GUI, one tab per Claude Code session, with live usage-limit tracking. Its mascot on the composer is Joulene.

DESIGN.md is the spec. §n always means a section of DESIGN.md.

GUIDE.md is the plan for the user guide in docs/: how its pages are written and their pictures taken, and which page describes each part of Deventry.

CHANGELOG.md says what changed in each version, for the people using Deventry, in the Keep a Changelog format; each release's notes come from it, and the guide links to it.

Working with the design

  • Read the relevant DESIGN.md sections before building a feature.
  • If the implementation has to differ from the design, update DESIGN.md in the same change. If the difference is a product decision (what the user sees or can do), ask first.
  • Build in the order given in §17 Milestones.
  • Record spike findings in DESIGN.md, replacing the spike note.
  • A change people would notice adds its entry under Unreleased in CHANGELOG.md, in the same change: what they can now do or what works differently, in the app's own words, not how it was built (GUIDE.md, "The changelog"). Refactoring, tests, CI and changes to the docs alone don't get one.
  • A new part of Deventry in DESIGN.md gets its row in GUIDE.md's map of pages, and its page in docs/, in the same change. A change people can see updates its page and retakes its pictures with tools/Deventry.Demo/guide-screenshots.ps1 (GUIDE.md, "Keeping it current").

Backlog

The backlog is the open GitHub issues on dotBunny/Deventry assigned to reapazor. The issues skill (.claude/skills/issues/SKILL.md) lists them, plans one and builds it once the user says go. A pull request that resolves one says Fixes #N; don't comment on the issue itself. Merging a pull request from someone outside dotBunny adds them to About's Contributors (AboutPage.ContributorList) if they aren't there yet, and keeps their commits theirs: update their branch with a merge rather than rebasing it.

The review-pr skill (.claude/skills/review-pr/SKILL.md) reviews a pull request in a review/pr-<N> branch of its own: what it changes and what's behind an option, its pros and cons, anything malicious or unsafe (read before it's built), and its cost in performance and tokens. It fixes what it finds there for the user to try, and once they approve, the branch goes back to the contributor's, so the pull request is merged on GitHub with its author's credit.

main holds what's released and dev what's being worked on for the next release. Work is done on a branch from dev, in this checkout or in a worktree beside it (../Deventry-<name>), and goes into dev when the user says so: a branch of several commits with a merge commit (git merge --no-ff, Merge branch '<name>'), a single commit straight on. A release merges dev into main (GUIDE.md, "Releasing"). Only a change to the site or the guide that can't wait for a release goes straight onto main, which GitHub Pages publishes, and main is then merged into dev. Pull requests, Dependabot's among them, go into dev. Once it's in, delete the branch and its worktree. A review's review/pr-<N> branches and worktree go once the pull request is merged or closed. Before deleting one, check it for uncommitted changes and for commits that aren't in dev, main or on a remote, and say which you deleted with their last commits, so they can be brought back. Don't push unless the user asks.

gh isn't installed on the development machine. Read issues, pull requests and workflow runs through GitHub's REST API instead (curl -s https://api.github.com/repos/dotBunny/Deventry/issues/<N>), which needs no sign-in for this public repository. A run's logs and artifacts do need one, so ask the user for them.

Stack

  • .NET 10, C# with nullable reference types on and warnings treated as errors, .editorconfig's code style included (unused usings, members and parameters, readonly fields, field naming). Package versions live in Directory.Packages.props. Settings → About lists every package the app ships with its license (src/Deventry.App/ThirdPartyNotices.targets, in the build): a new one under a license with no text in src/Deventry.App/Licenses/, and without a license file of its own, fails the build until its text is added there.
  • Avalonia 12 with MVVM (CommunityToolkit.Mvvm). Markdown is rendered with LiveMarkdown.Avalonia.
  • Layout (§13):
    • src/Deventry.Core: sessions (with rewinding files, MCP servers' status and their requests for input, and the sessions running on this machine from Claude Code's sessions folder: Sessions/LiveSessions, and the task list it keeps in its tasks folder: Sessions/ClaudeTasks), protocol (with typed views of the system messages Deventry reads, Protocol/SystemNotices, hook callbacks, protocol logs with secrets redacted, and Diagnostics counts), permission rules, folder trust (what a folder's own Claude Code configuration would run, Claude/FolderTrust), sign-in (claude auth login/logout and the sign-in control requests), accounts besides the Default one (Accounts/: API-key accounts, which one a folder uses, the key helper's command and what moving a tab costs), install checks (with installing Claude Code, Installation/ClaudeInstaller, and the Claude desktop app's files, which are never run: Installation/ClaudeDesktop), settings and state stores and settings sync (what syncs, which Settings marks the rest by: Settings/SyncedSettings), transcripts and History (with the search through Claude's replies), the session library and leases, diffs and changed files (with reverting a hunk or file, and what each change left a file holding: Diffs/FileAfter), external diff tools, git (project identity, working tree, worktrees: Git/GitWorktrees, Claude moving into and out of one: Git/WorktreeMove, and which to clean up: Git/WorktreeCleanup), check-in timing, Claude Code updates, source builds (copies to run from, noticing new builds, restart snapshots, and the checkout's new commits upstream, pulled and built: Development/SourceCheckout), Deventry's own updates (Updates/: versions, the GitHub release feed, the downloader; IAppInstaller), the composer's data (Composer/: slash commands, the @ file index and matcher, mentions of other sessions and agents: SessionMentions, attachments and large pastes, drafts and the stash: DraftStore), Perforce (Perforce/: workspace detection, the ticket keeper, changelists, and for files their states from p4 fstat and the cache of them (PerforceFileState, PerforceStatusCache), each folder's workspace found in the background (PerforceWorkspaceFinder) and the file commands (PerforceFileCommands); the passwords' store is Credentials/ICredentialStore), the user environment (Processes/UserEnvironment: the environment of claude and the user's other programs, with the login shell's merged in; ILoginShell), project tools (ProjectTools/: project types, IProjectType, with the settings each declares, and detection; the Unreal, Unity and Godot types, which the App lists in ProjectToolsService.AllTypes; jobs, deventry.json actions and links, .bat quoting, the logs the engines' editors write: ProjectTools/EditorLogs, reading one without holding it, which are being written, where one moves to (Unity's to the project's own), and their lines as entries; ISystemProcesses, IUnrealEngineRegistry), Remote Control (RemoteControl/: the remote_control request and its replies, eligibility, the presence file; ISleepBlocker), starting at login (LoginItems/: which copy of Deventry the login entry starts, and the command it runs; ILoginItems), Claude's service status (Status/: the status page's summary, which services and incidents count, and which tab errors call for a check), scratch pads (ScratchPads/: which pad a folder's project has, its file and revisions, syncing it through the session library, conflict copies), accessibility (Accessibility/: reduced motion, Claude Code's prefersReducedMotion, the zoom steps; ISystemMotion), Claude Docs (Docs/ClaudeDocCall: the doc a call to claude.ai's Docs connector made or changed), the Project Files tab's data (ProjectFiles/: a folder's entries, new files' and folders' names, renaming and deleting (the trash is Files/ITrash), reading and saving text files, line endings and indentation, what a change on disk does, Claude's changes across a project's tabs and their gutter marks, unsaved edits kept on disk; watching folders is Files/IFolderWatcher), threads (Threads/: a thread's SendMessage calls as its hook sees them, its sub-threads' names, and everything Claude is told about threads), subagents (Subagents/: what Claude is told, and the hook answers, when they're discouraged or turned off), and the demo (Demo/: what demo mode and tools/Deventry.Demo write, DemoHome its settings and tabs, DemoContent its projects and transcripts, DemoHistory; and StandInClaude, Deventry answering as Claude Code in the demo).
    • src/Deventry.Usage: plan usage parsing, the SQLite usage history (UsageStore, with its file and schema in UsageSchema), burn rate and projection, alerts, the polling schedule, the file machines share their plan usage in (UsageSharing) and the one in each synced session with its turns (SessionUsageFile), and the demo's usage history (Demo/DemoUsage). No UI.
    • src/Deventry.Platform: OS-specific code: the process monitor (Job Objects on Windows, /proc, ps), notifications and the Dock/taskbar badge, the icon's animation and the taskbar flash (WinRT toasts, UNUserNotificationCenter, notify-send), the Windows jump list, the single-instance pipe, where macOS's traffic lights are, for a screenshot to draw them in (Shell/Mac/MacWindowButtons), the OS credential store for Perforce passwords and accounts' API keys (Credential Manager, the Keychain through Security.framework, secret-tool), processes by name for project tools (Processes/SystemProcesses), Unreal's registry entries (ProjectTools/), the installers for Deventry's own updates (Updates/: the MSIX update through PackageManager, and update-helper.sh, which swaps Deventry.app on macOS), reading the login shell's environment on macOS and Linux (LoginShell/, behind Core's ILoginShell), keeping the computer awake (Power/: SetThreadExecutionState, caffeinate, systemd-inhibit; behind Core's ISleepBlocker), the login entries (LoginItems/: the Run key and the MSIX's startup task, a LaunchAgent, an XDG autostart file; behind Core's ILoginItems), the OS's reduce-motion setting (Accessibility/: SPI_GETCLIENTAREAANIMATION, accessibilityDisplayShouldReduceMotion, GNOME's enable-animations; behind Core's ISystemMotion), and the trash a Files tab deletes to (Files/: the Recycle Bin through IFileOperation, the macOS Trash, gio trash; behind Core's ITrash). No UI. Windows and macOS APIs are called through source-generated COM interop, P/Invoke and the Objective-C runtime, so the project stays a plain net10.0 library.
    • src/Deventry.App: the Avalonia UI.
      • ShellViewModel holds the tab groups and History, with a partial for the command palette and the next tab waiting (CommandPaletteViewModel filters the palette), and partials for worktree tabs, threads (ShellViewModel.Threads.cs: who belongs to which thread, keeping sub-threads under it), the panes (ShellViewModel.Panes.cs: tabs side by side, the selected one focused; TabViewModel.IsShown is on screen, IsSelected focused), the Project Files tabs (ShellViewModel.ProjectFiles.cs: opening, closing and restoring them, and opening files in them from elsewhere), Changed files across the tabs (ShellViewModel.ChangedFiles.cs: which tab changed a file since, and the others looking again) and the hosts it implements. A pane holds an IShellTab, which a session tab and a Files tab both are: Selected is the focused one, SelectedTab that tab when it's a session and SelectedFiles when it's a Files tab; AllTabs is sessions only. Four parts are classes of their own: SessionOpener opens a session from History (this machine's copy or the library's, taking over or opening a copy, conflict copies) through ISessionOpenerHost, which the shell implements in ShellViewModel.Hosts.cs; ShellLayout (shell.Layout) holds the sidebar's width and collapse, and the room the tool windows take at the right; ToolWindows (shell.ToolWindows, ViewModels/ToolWindows/: ToolDockViewModel, its groups, windows and kinds) is the tool windows' dock, Rider's strip of icons, one for the window, with ShellViewModel.ToolWindows.cs choosing the session they show and giving Games and Find their content; GroupColors chooses a new group's color and remembers a folder's; and GroupNames remembers the name the user gave a folder's group.
      • TabViewModel is one session, split into partial files for the library, notifications, the composer (with PromptRecall), drafts, the stash and quoting, messages held until the turn ends, sign-in, folder trust, agents, running tasks, the working line, auto-continue, usage credits (TabViewModel.UsageCredits.cs: a tab that doesn't use them holds and stops), Ultracode and output styles, the mark and the reviewed icon over it (TabViewModel.Mark.cs; Views/MarkIcon draws both), worktrees and extra folders, the git branch on its row, the tool windows (TabViewModel.ToolWindows.cs: which show the tab, and its ways to them), the scratch pad, rewind and branch, find and export, threads (TabViewModel.Thread.cs: a thread's SendMessage hook, approvals, report and the strip of sub-threads waiting; a sub-thread's deliveries), subagents (TabViewModel.Subagents.cs: whether Claude may use them, with its hook and note; their rows under the tab, each a SubagentRowViewModel; and one's work shown in the tab, ViewedAgent, which Views/TabView shows in a second list beside the conversation's), and the other sessions on this machine (TabViewModel.Messaging.cs: the session's name in Claude Code, the name it's reached by, and who the composer's @ can name). The scratch pad is a ScratchPadViewModel that Services/ScratchPadService shares between the tabs of a project. Six areas are child view models the tab owns: ProcessMonitorViewModel (tab.ProcessMonitor), ChangedFilesViewModel (tab.ChangedFiles, its rows in Files, grouped by working tree in ChangedFilesViewModel.WorkTrees.cs), ProjectToolsViewModel (tab.ProjectTools, whose menu ProjectMenu.Build makes, and ProjectRunsViewModel, tab.ProjectTools.Runs: the runs of its jobs in Items, each a ProjectRunViewModel with its log, the running job and its process tree; and the editor logs under the tab in tab.ProjectTools.Logs, each an EditorLogViewModel that ShellViewModel.EditorLogs.cs puts under one of its project's tabs; both logs are LogTabViewModels), RemoteControlViewModel (tab.RemoteControl), PerforceViewModel (tab.Perforce: ticket handling and the changelist) and ContextViewModel (tab.Context: the context indicator and ring, from ContextIndicator.From, and the token counts). Each gets what it needs from the tab through its own small host interface (IProcessMonitorHost and so on) over the ITabAreaHost they share, which the tab implements in TabViewModel.Hosts.cs (the runs get theirs from project tools, IProjectRunsHost); where one area needs another, such as the process monitor showing the project job's processes, it goes through the tab. McpServersViewModel (tab.McpServers, the MCP page) takes the session and a way to open links rather than a host. SettingsViewModel is the Settings window, which isn't modal: the sidebar's pages in groups (SettingsSidebar), the search box and the page shown, following the main window's selected tab through ISettingsHost (ShellViewModel.Settings.cs), with partials for the selected tab's project group and the version and Help at the sidebar's foot. Each page is in ViewModels/Settings/ (GeneralPage, ClaudeCodePage and so on, on SettingsPage, sharing a SettingsContext), which declares its sections and settings (SettingsGroup, SettingItem, keyed by their paths in settings.json): what search finds, the changed mark and Reset, This computer, the tabs that have their own, and each section's Reset to defaults. Its view in Views/Settings/ draws them with Controls/SettingsSection and Controls/SettingRow, which find their setting by its key (their look is Themes/SettingsStyles.axaml), and so does Tab settings (TabSettingsViewModel). Project tools is a group of pages: AllToolsPage, then a ProjectTypePage for each project type, drawn from what the type declares, with its icon from ProjectTypeIcons (which the sidebar's project row uses too). Source control is a group too: GitPage (whose Path to git every git Deventry runs follows, through GitWorkingTree.ConfiguredPath), then PerforcePage. The project group's pages (ProjectLinksPage, ProjectActionsPage, ProjectToolChoicesPage) show ProjectSettingsViewModel. Keyboard records shortcuts, its quick suffixes' too, through the context's ShortcutRecorder.
      • Conversation/ConversationBuilder turns session events into conversation items, every one drawn as a Controls/Card (§18, "Conversation cards"), with a run of tool calls in one ToolGroupItem (its tiles laid out by Controls/TilesPanel; ConversationTree walks cards and subagents for whatever looks through the conversation), with HookRuns (the hook run rows), PendingPrompts (the prompt and MCP input cards Claude Code can still withdraw) and StreamingBlocks (the reply and thinking rows still streaming); PromptItems are the permission, question and plan cards. Conversation/AgentMap and AgentNode are the agent map (§18), kept by ConversationBuilder from the same routing as the subagent groups, as is Conversation/RunningTasks, the tasks Claude Code keeps running in the background. Conversation/TodoList is the pinned to-do list and the Tasks page, kept in step with the list Claude Code keeps by TodoListFolder, with the plan's versions, what each task did and the Claude Docs Claude wrote (TodoList.Docs.cs); ConversationSearch is find, ConversationExport the Markdown and HTML export, and McpInputItems the cards for MCP servers asking for input.
      • Services/UsageTracker and Services/LibraryService connect the usage engine and the session library to the tabs; Services/AccountsService keeps the accounts besides Default, their keys and their spend warnings, and says what a tab's claude starts with for one (TabViewModel.Account.cs is the tab's side, ShellViewModel.Accounts.cs the groups', AccountSpendViewModel the header's spend, and Services/ApiKeyHelperLaunch the key helper that Program.Main runs); Services/PerforceFilesService (services.Perforce.Files) is Perforce for files: the Files tab's marks and menus (ProjectFilesViewModel.Perforce.cs), and opening files in Perforce as they change, with CheckOutBeforeWritingAsync for any feature that writes files; Services/RemoteControlService holds what the tabs share about the Claude app (eligibility, the presence file, which tabs are connected); Services/SourceUpdateService checks a source build's checkout for new commits, and pulls and builds them for RestartService; Services/KeepAwakeService keeps the computer awake, as Settings → General and the header's cup say and for tabs connected to the Claude app; Services/ServiceStatusService checks Claude's status page for the header's dot and banner; Services/LiveSessionsService watches Claude Code's sessions folder for the sessions running on this machine and their names; Services/EditorLogService looks for the tabs' projects' editors and reads their logs; Services/DraftService keeps the tabs' drafts and the stash on disk; Services/WorktreeCleanupService removes worktree tabs' worktrees as Settings → General says; Services/MascotService holds Joulene on the composer for every tab; Services/UnfocusedWindows keeps every window from taking the focus for a screenshot run, and Services/Automation/ takes the scripts' commands (DEVENTRY_AUTOMATION_PIPE), carrying them out through the controls' automation peers. Demo/ is demo mode (DESIGN.md §11, "Demo mode"): DemoLaunch writes a demo and starts a second Deventry on it, DemoStarter is Try the demo, DemoMode says this Deventry is one (AppServices.Demo), and StandInClaudeLaunch is what Program.Main runs as a demo's claude.
      • Mascot/ is Joulene on the composer (§5): MascotDirector decides what she does, step by step on the injected clock, from what the selected tab is doing (MascotSituation, which TabViewModel.Mascot.cs tells it) and what happens (MascotDirector.Events.cs), with MascotAntics' steps, MascotArt's poses, props and hats (Assets/Mascot/mascot.json, which packaging/icon/build-icons.mjs draws from the icon's sprite) and MascotCalendar's hat for the day. Views/MascotLayer sits behind the composer box (and a second one in front of it), works out where on its edge she has room, follows the pointer, carries her and shows her bubble; Views/MascotFigure draws her. The README's animation comes from MascotPictureTests.
      • Games/ is the games against Joulene (§18, "Games with Joulene"): the rules and her play, with no UI (TicTacToe, ConnectFour, Hangman and their opponents, HangmanWords, and Minesweeper, whose mines are hers). Services/GameService holds the one game open for the whole app, its score and level in state.json, and tells her what happened through MascotService; each game's view model is on GameViewModel, the tab's side is TabViewModel.Games.cs and the shell's ShellViewModel.Games.cs, and Views/GamesView is the Games tool window's, made only while it shows.
      • ProjectFilesViewModel is a group's Project Files tab (§18, "Project files"; TabGroupViewModel.Files), with partials for the explorer, new files and folders, the open files, finding a file by name and Claude's changes; each open file is a ProjectFileViewModel. Finding and replacing in a project's files is FindInFilesViewModel (the popup, which ShellViewModel.FindInFiles.cs opens), FindResultsViewModel (a search kept in the Find window, whose searches are FindWindowViewModel) and FindInFilesSearch, over Core's ProjectFiles/FindInFiles, FindMatcher and FindReplace; Views/FindInFilesView and Views/FindResultsView show them. Editor/ holds IEditorDocument (AvaloniaEdit's document in the app, a string's in view model tests), the files' languages, and a Markdown file's preview (§18, "Markdown preview": MarkdownPreview parses the text for LiveMarkdown's renderer, PreviewScroll maps its lines to places for scrolling with the editor). Views/ProjectFilesView and Views/ProjectFileView show them, and Views/Editor/ is the editor: CodeEditor, the TextMate coloring fed off the UI thread (TextMateLines, TextMateColoring), the gutter's ChangeMargin, the peek (ChangePeek, ChangePeekGenerator), EditorRenderers and the preview's MarkdownPreviewView. Markdown shown anywhere gets its images and code blocks' Copy from an IMarkdownHost: a session's tab, or a file's preview. Their look is Themes/ProjectFilesStyles.axaml. Views/DiffLines is the diff view's lines, which the diff window and a Files tab share.
      • Views/TabView is one tab: the conversation and its templates, find, the prompts over it, the marks in the scroll bar and F3/F4 (Controls/ScrollMarks, Conversation/ConversationLandmarks), and a pane's header. Views/ToolCallWindow is a tool call in a window of its own (ToolCallWindowViewModel). The tool windows are Views/ToolStripView (the strip of icons, and dragging them), ToolPanelView (a group's window, with its header), ToolWindowMenu (a window's menu), ToolWindowView and SessionPagesView (a session window's page, which was the side panel's, with ScratchPadView for the scratch pad's), placed by ShellView (ShellView.ToolWindows.cs: the grips, sharing the right side, and a click outside an unpinned window). ComposerView (the composer, with ComposerView.Assist.cs for the autocomplete and attachments) sits inside the tab's view, ScratchPadAdd is the conversation's Add to scratch pad (the menu of selected text and the code blocks' button), and the styles all three share are in Themes/ConversationStyles.axaml. SettingsWindow shows one page view from Views/Settings/ at a time. Shared UI plumbing lives beside the view models: UiTicker and UiTimeout (timers on the injected clock), InlineConfirmation (a step that asks first) and Core's Formats (times, durations, sizes and names as the UI writes them).
    • tests/: Deventry.Core.Tests (unit and protocol replay), Deventry.Usage.Tests, Deventry.Platform.Tests, Deventry.App.Tests (view models, and a RealCli test that drives a tab with the real claude), Deventry.App.UiTests (rendered views, headless, with Verify snapshots), Deventry.IntegrationTests (real processes, the recording tests and the Live suite).
    • tools/: Deventry.FakeClaude (the fake-claude test double), Deventry.MockApi (a mock Messages API), Deventry.Fixtures (record mode: a protocol log to a cleaned fixture) and Deventry.Demo (deventry-demo: demo projects, tabs and usage history to run Deventry on with fake-claude, written by Core's and Usage's Demo/ with the projects as git repositories, and screenshots.ps1, which takes the README's screenshots in docs/screenshots/ from it, and guide-screenshots.ps1 the guide's in docs/screenshots/guide/, with the helpers both use in Screenshots.psm1, which drive Deventry through its command channel and never with the mouse or keyboard, so a run leaves the user's focus alone, ci-screenshots.ps1, which takes the demo's main window, Settings and About on whichever system it runs on, through the command channel alone, for .github/workflows/screenshots.yml to keep each system's as an artifact, and social-card.ps1, which draws the site's social card, docs/assets/social-card.png, from social-card.html).
    • compat/: the compatibility surface list, check script and snapshots (§16).
    • packaging/: the MSIX and .dmg build scripts, manifest, Info.plist, entitlements and icons (icon/build-icons.mjs draws them all); .github/workflows/package.yml runs them (§2, "Packaging and signing"). release-notes.sh prints a version's notes from the changelog, and release.mjs makes a release's changes to the changelog, the guide and the version (GUIDE.md, "Releasing").
    • docs/: the user guide, a Jekyll site that GitHub Pages builds from main and publishes at https://deventry.app, so everything in it is public (GUIDE.md): the landing page (index.html, with a layout, stylesheet and script of its own), the guide from /guide/ with a folder of pages per chapter and the chapters grouped into sections (guide/), Deventry's look for its theme in _sass/, _includes/shot.html for a picture in the reader's theme, and the pictures in screenshots/ (the guide's in screenshots/guide/). .github/workflows/docs.yml checks its links and pictures.
  • Deventry.Core, Deventry.Usage and Deventry.Platform must not reference Avalonia.
  • Development happens on Windows, but the app must also run on macOS and Linux. Keep OS-specific code behind interfaces, in Deventry.Platform.
  • In XAML:
    • Use the app's own color tokens from App.axaml (MutedTextBrush, DividerBrush and so on), not Fluent's internal resource names.
    • The Claude style (Settings → Appearance → Style) overrides surface, text, accent and caution tokens from Themes/ClaudeColors.axaml, and Themes/AppColors gives Fluent a matching palette. A new token of those kinds needs a Claude value there too, in light and dark. Its shapes are styles under the claude class (as Density's are under compact); put a view's own look in a style rather than in attributes, so the class can change it.
    • Those classes are on a controls:AppearanceScope, and a style names it first: controls|AppearanceScope.claude Border.composer, never .claude Border.composer. A selector that starts with a bare class and then looks inside makes Avalonia watch the classes of every ancestor of every control it might style, which took 140 MB for six tabs. AppearanceScopeTests checks every .axaml file.
    • Reference Application.Resources from Application.Styles with DynamicResource, because styles load before resources.
    • Give icon-only buttons an AutomationProperties.Name, and a button whose content is a panel one too: without it, it's read as the panel's type.
    • A menu in sections, as the header's ≡, a tab's and a folder group's are, has the class sectioned, a MenuItem with the class menusection for each section's title, and a Path with the class menuicon for each entry's icon (Themes/MenuStyles.axaml).
    • Put text in another font or size beside other text (monospace beside the interface font) on its baseline with controls:Baseline.AlignWith="{Binding #Name}", not a margin tuned by eye.
  • The visual reference is JetBrains Rider (its New UI, Islands theme) for the Standard style, with the conversation's layout after Claude Code's VS Code extension, and the Claude apps (iOS, claude.ai) for the Claude style; both styles are islands (§3, "Visual style").

Rules that keep the code testable (§15)

  • Talk to Claude Code only through IClaudeTransport.
  • Start processes only through IProcessLauncher.
  • Use the injected TimeProvider for anything time-based. Never use DateTime.Now, DateTime.UtcNow or real delays in logic.
  • Order work so no other thread can see it half done, and don't count on the UI thread to queue it in order. Each of these was a race a flaky test found:

    • Set what work reads before anything can start or see it: a flag before the tab is shown (showing a tab starts it), a request's deadline before the request goes out, Working before the message is written.
    • Check and record in one locked step, never two.
    • Let the latest result win: a refresh that a later one has overtaken drops its rows instead of replacing them.
    • Don't start a timer or callback while holding a lock it takes: a clock can run it at once, on the same thread.
    • A stopped process isn't gone until the OS says so; wait for it before reporting it ended.

    View-model tests run continuations on the thread pool, so races like these show up there first.

  • Never hard-code paths to the app data folder, the session library or ~/.claude. Inject them.
  • Secrets (Perforce passwords, accounts' API keys) go only to the OS credential store (ICredentialStore) and to child processes on standard input (ProcessRunner's inputLine). An API key also reaches claude through Deventry's own key helper, which prints it for the claude that ran it (Accounts/ApiKeyHelper, DESIGN.md §4, "Accounts"). Never in arguments, settings, state, the environment or logs.
  • The build enforces these: src/BannedSymbols.txt bans the clock, sleeps, timeouts without a TimeProvider, Process.Start, new HttpClient and Environment.SetEnvironmentVariable under src/. Don't add a #pragma warning disable RS0030 unless the code is the one place that has to (like ProcessLauncher), and say why on the same line.

Claude Code integration (§13, §16)

  • Any new use of something from Claude Code goes in compat/surface.yaml in the same change. That includes flags, environment variables, message types or fields, control requests, settings keys, file paths and command output.
  • Prefer documented behavior. If you rely on undocumented behavior, mark the entry undocumented and record a fallback.
  • Always launch claude with an environment from ClaudeEnvironment (ClaudeEnvironment.From, starting from UserEnvironment.GetAsync), which removes the session variables Claude Code sets for its child processes (ClaudeEnvironment.SessionVariables, §13). Otherwise a claude started from inside a Claude Code session behaves as a child session. Don't strip by prefix: CLAUDE_CONFIG_DIR, CLAUDE_CODE_USE_BEDROCK and similar are user configuration and must pass through.
  • Start the other programs Deventry runs for the user (git, p4, diff tools) through UserEnvironment.ApplyAsync (or Apply), so they get the same environment and PATH as claude, including the login shell's (§13, "Login shell environment").
  • Read Claude Code's fields in Core: give a message or system subtype a typed view (as SystemMessage.HookRun) rather than reading Raw in the App.
  • A write to a claude that has exited fails with a broken pipe, which says nothing; its exit says why (ClaudeSessionExitedException, with its error output and startup failure). Report the exit, as ControlChannel does for control requests.
  • Parse tolerantly:
    • Ignore unknown fields.
    • Skip unknown message types.
    • Map unknown enum values to Unknown.
    • Never let one bad line end a session.
  • Detect features from the capabilities list on system/init, not by comparing version numbers.
  • References:
    • Claude Code docs: https://code.claude.com/docs. Add .md to any page URL for Markdown; https://code.claude.com/docs/llms.txt lists every page.
    • The Agent SDK TypeScript reference (/docs/en/agent-sdk/typescript) is the most complete description of messages and control methods.
    • The Python Agent SDK source (github.com/anthropics/claude-agent-sdk-python) shows the control message wire format.

Spikes

spikes/ holds the throwaway Node scripts from the protocol spikes: a mock Messages API, a stream-json driver and scenario files. See spikes/README.md.

  • Use them as a working reference for the wire protocol, and to check how a new Claude Code version behaves.
  • They aren't part of the app or the build. Don't extend them into app code; the .NET test harness replaces them.
  • scenarios/10-real-usage.json uses real tokens on the user's account. Only run it when asked to.

Tests

  • Tests must never call the real model or use anyone's account or tokens. Use the fake transport, fake-claude, or the mock Messages API.
  • Tests never reach the network. Deventry's own requests (the update check and download) go through AppServices.Http; TabTestHarness gives it a handler that refuses every request unless a test passes a fake one. Never run a real install in tests: use a fake IAppInstaller.
  • Tests that run the real claude binary are tagged [Trait("Category", "RealCli")] and must:
    • point it at MockAnthropicApi with ANTHROPIC_BASE_URL and a dummy ANTHROPIC_API_KEY,
    • set CLAUDE_CONFIG_DIR to a temporary folder,
    • set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1,
    • never read or write the real ~/.claude,
    • skip themselves (Assert.SkipWhen) when Claude Code isn't installed.
  • Tests tagged [Trait("Category", "Live")] use real tokens. Only run them when asked to. They read DEVENTRY_LIVE_API_KEY (a separate, spend-limited key) and DEVENTRY_LIVE_CONFIG_DIR (a signed-in config folder) and skip without them.
  • Never use a real Perforce server or real Perforce credentials: use FakeP4 (tests/Deventry.Core.Tests/Support/). The macOS Keychain round trip runs only with DEVENTRY_TEST_KEYCHAIN=1.
  • UI snapshots: a changed view writes *.received.txt next to its *.verified.txt in tests/Deventry.App.UiTests. Read the difference; if the change is intended, rename the received file over the verified one. In a UI test, await Verify(...) resumes off the UI thread, so make it the last step.
  • Test time-based behavior with FakeTimeProvider, not sleeps. To wait for real work (a process, a background thread, rendering), use Waiting.UntilAsync (tests/Shared/, in every test project) or TabTestHarness.Eventually, which go by the clock; never a loop count or a fixed delay. Where something must not happen, wait for the moment it would have; only when nothing marks that moment, use Waiting.NeverAsync.
  • Wait for the state the next step reads, not one that comes before it, stands in for it, or is still true from the step before. Half the flaky tests fixed so far were this:
    • A tab is idle before a turn arrives as well as after it, and the last item is still the turn before's footer: count the footers.
    • Its status says Working a moment after IsInTurn, and a turn's end and the Idle after it are two events (IsSettled waits for both).
    • A setting that's stored isn't yet applied, and a request that's started isn't yet answered or written.
    • A settled tab's changed-file rows come later, read off the UI thread.
    • Markdown parses and lays out in the background, and a laid-out window isn't rendered (UiText.HitTest renders it first).
  • Bound real work with Waiting.Timeout, never a shorter time picked for the case: it's how long a hung test takes to fail, not how long the work may take. A Windows runner's disk has stalled writes for two minutes. A bound on how fast something runs is an order of magnitude above what it takes: CI runs every test project at once on a few cores, where a 10 ms diff has taken 104 ms.
  • View-model tests have no UI thread. Start work as a click does (InlineDispatcher.RunAsync), so it doesn't resume on the pool outside the harness's UI lock. Fakes and lists that sessions write to from their own threads use locks or concurrent collections, and are read as copies. Blocking work goes on a thread of its own (TaskCreationOptions.LongRunning), not one a busy pool may not hand out for seconds.
  • Write each test for all three systems. Development is on Windows, so a test passing there says nothing yet about macOS and Linux. What has differed:
    • macOS's temporary folder is a link (/var is /private/var), which git resolves: compare paths through RealPath.
    • Windows won't rename over, or delete, a file that's open or read-only (FileRetry; the harness clears git's read-only objects).
    • git takes a file's executable bit from the file on macOS and Linux (File.SetUnixFileMode), but only from the index on Windows.
    • macOS writes Esc as ⎋: take shortcut text from ShortcutTips, as the app does.
    • The macOS runner is arm64, and Path.Combine builds a path with the running system's separator, so another system's path is written out.
    • .NET's file patterns follow Windows' rules everywhere: settings.json.* matches settings.json.
    • Case, line endings and the order a folder lists its entries in differ too.
  • Run tests/Deventry.App.UiTests before pushing a change to a view, a style or a color token: a snapshot left stale fails CI on every system.
  • When CI is red or a test is flaky, the ci-failures skill (.claude/skills/ci-failures/SKILL.md) finds the cause: collect the failures, tell a slow runner from a race, reproduce it, fix it, prove it.
  • Recorded protocol fixtures live in tests/Deventry.Core.Tests/Fixtures/protocol/<claude-version>/. Remove paths, emails and account details before checking one in: record them with ProtocolRecordingTests (DEVENTRY_RECORD_FIXTURES=<folder>, against the mock) or convert a protocol log with tools/Deventry.Fixtures, both of which clean them, then read the result before committing.
  • xunit.v3 stays on 3.2.x until Avalonia.Headless.XUnit supports 4.x.

Commands

dotnet build Deventry.slnx
dotnet test Deventry.slnx --filter "Category!=Live"                  # everything free (RealCli skips without Claude Code)
dotnet test Deventry.slnx --filter "Category!=RealCli&Category!=Live" # no Claude Code needed
dotnet test Deventry.slnx --filter "Category!=Live" --collect "Code Coverage;Format=cobertura" --results-directory TestResults  # coverage, locally only (not in CI)
dotnet run --project src/Deventry.App -- --folder <path>             # uses your real account: messages cost usage

Each project has a packages.lock.json, and CI restores in locked mode: after changing a package, restore and commit the lock files with it.

global.json pins the .NET SDK (10.0.400, or a later 10.0.4xx). If dotnet says it isn't found, don't work around it and don't change global.json:

  • If ~/.dotnet (DOTNET_ROOT) has it, use that dotnet by putting the folder first on PATH. A dotnet elsewhere on PATH, such as C:\Program Files\dotnet, only sees its own SDKs.
  • Otherwise install it there with Microsoft's install script from https://dot.net/v1/: dotnet-install.ps1 -JsonFile global.json -InstallDir ~/.dotnet on Windows, dotnet-install.sh --jsonfile global.json --install-dir ~/.dotnet elsewhere. .claude/hooks/session-start.sh does the same for web sessions.

Run the app without using tokens by pointing it at the mock API:

dotnet run --project tools/Deventry.MockApi -- 8787    # in one terminal
# in another, with these set: ANTHROPIC_BASE_URL=http://127.0.0.1:8787  ANTHROPIC_API_KEY=sk-ant-mock
#                             CLAUDE_CONFIG_DIR=<a temp folder>         CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
#                             DEVENTRY_HOME=<a temp folder>   (keeps Deventry's settings and saved tabs out of your profile)
dotnet run --project src/Deventry.App -- --folder <path>

A source build (anything run from a bin folder in this checkout, including dotnet run) copies itself to builds in the data folder and runs from there, so you can rebuild while it runs. It offers to restart into each new build, keeping every tab and draft, and takes its tabs back if the new build doesn't start (§9, "Working on Deventry"). dotnet run returns once the copy has started. Set DEVENTRY_RUN_IN_PLACE=1, or attach a debugger, to run in place instead.

Stop the app by closing its window, not by killing the process: closing interrupts running turns and stops each tab's claude and everything it started. A killed app leaves them running; the Job Objects deliberately don't kill on close, so a user can keep a tab's processes (§4).

View model tests use tests/Deventry.App.Tests/Support/TabTestHarness.cs: a scripted Claude Code connection, a fake clock and an inline dispatcher.

For a populated window without an account (seven tabs over three game projects in git, Unreal, Unity and Godot, with changed files, marks, a thread with two sub-threads, a plan, tasks and subagents, past sessions in History over seven projects, one in a worktree, a usage header with history and a projection, and an API-key account, Studio, with two tabs on it and a month of its spend), dotnet run --project tools/Deventry.Demo -- <folder> writes the demo and prints the environment to start Deventry with; see the header of its Program.cs. On Windows, tools/Deventry.Demo/screenshots.ps1 -Projects <short folder> retakes the README's screenshots in each style and theme, and guide-screenshots.ps1 -Projects <short folder> the guide's (-Only usage/limit or -Only usage for a picture or a chapter's). The projects' paths show in the conversation, so give it one that says nothing about you, such as C:\Demo.

To show the user a branch, run it on the demo beside their own Deventry: write the demo to a folder of its own, start dotnet run --project src/Deventry.App with the environment it prints, and tell them it's there. The single-instance pipe and the builds copy follow DEVENTRY_HOME, so it never hands off to theirs. Don't set DEVENTRY_AUTOMATION_PIPE for it: that keeps its windows from taking the focus, which gets in their way. Edit its settings.json only while it's closed, as it saves over the file when it quits; restart it by closing its window. To look at it yourself, capture its window with PrintWindow rather than bringing it forward, and read pixel colors along a row of the capture rather than judging by eye. For a menu or a window it opens, start a copy through tools/Deventry.Demo/Screenshots.psm1 (Start-Deventry, Send-RightClick, Get-OtherWindow, Save-Window, Stop-Deventry), as the screenshot scripts do, which never takes the focus, on a demo folder no other copy has open. Delete the demo's folder once you're both done with it.

To see the usage header (meters, sparkline, projection) without a subscription, set Settings → Claude Code → Path to claude to fake-claude (built next to the integration tests) and run with FAKE_CLAUDE_USAGE=demo: its plan starts at 35% of the session and climbs 0.6% a minute. FAKE_CLAUDE_USAGE=credits is a used-up session that usage credits pay for, for the notice under the header, and FAKE_CLAUDE_USAGE=<path> answers with a recorded get_usage response instead.

Prompts the mock understands: WRITE_FILE <path>, READ_FILE <path> (an image comes back as one), EDIT_FILE <path>, RUN_BASH <command>, ASK_QUESTION, EXIT_PLAN (in Plan mode), SLOW, API_ERROR (two 529s, then a reply), SUBAGENTS (a nested fan-out with a permission prompt inside a subagent), LONG_AGENT (a subagent to stop; in the background with BACKGROUND), SUBAGENT_WRITE <path> and SUBAGENT_EDIT <path> (a subagent that writes or edits the file; in a worktree of its own with ISOLATED after it), ENTER_WORKTREE <name> and EXIT_WORKTREE keep|remove (Claude moving into a worktree mid-session, and out again), AGENT_REPLY <text> and SEND_MESSAGE <to> | <message> (a thread's SendMessage); anything else gets pong. The environment may set CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH, which passes through to claude as user configuration, so a RealCli test that needs nesting sets it.

fake-claude's prompts: ASK_PERMISSION, SLOW, CRASH, SPAWN [seconds] [busy] (a child process for the process monitor), SILENT (waits for a check-in), HANG, AUTH_FAIL, LIMIT [minutes] (a turn stopped at the session limit, which resets in that many minutes), RUN_BASH <cmd> (calls back PreToolUse hooks), SUBAGENTS, EDIT_FILE <path> [| <path>…] (an Edit of each file that reports a one-line change without touching it, so the turn ends with Modified files), DOC (a doc written with claude.ai's Docs connector) and SUGGEST <text> (a prompt suggestion after the reply, when Settings → Claude Code → Suggest my next prompt is on); a message with images gets a reply naming them. It keeps its sign-in in fake-claude-auth.json in CLAUDE_CONFIG_DIR: run with FAKE_CLAUDE_LOGGED_IN=0 to see the sign-in screen, and use FAKE_CLAUDE_LOGIN=auto|code|fail|hang to exercise sign-in without an account. The header of its Program.cs lists the rest. An API key has no plan limits, so the usage header stays empty against the mock; the usage tests cover it.

Compatibility check (§16): node compat/check.mjs detect, then report or update-snapshots. See the header of compat/check.mjs.

Protocol fixtures (§15): DEVENTRY_RECORD_FIXTURES=<folder> dotnet test tests/Deventry.IntegrationTests --filter "FullyQualifiedName~ProtocolRecordingTests" records the scenarios against the mock; dotnet run --project tools/Deventry.Fixtures -- <protocol.log> <fixture.jsonl> --root <project folder> converts a log from Settings → Advanced → Log protocol traffic.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.