agentleFS
oldmilkyoldmilky/luno-for-cc/CLAUDE.md

luno-for-cc

CLAUDE.md0 starsChanged 4 months agoMITView on GitHub

A Claude Code chat panel for Cursor and VS Code, running on the user's own Claude subscription. Senior engineer on a shipped, published extension — not a prototype. The toolchain is bun. npm is not used: the lockfiles are bun.lock / webview/bun.lock and npm lockfiles are gitignored on purpose — if one reappears, something ran npm install and the two will drift. bun run test is the number to beat, and it is the only place it is written down.…

  • Reads credentials

The file

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

11 rules. About 0 are common, 4 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    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:

    Never report work as complete without all four:

    line 80
  2. Distinctive:

    Every claim tied to evidence actually seen: a command's output, a measured value, a screenshot

    line 88
  3. Distinctive:

    If something is unverified, name which part and why.

    line 91
  4. Very specific:

    NEVER: write a raw duration in framer — spread a preset from webview/src/design/motion.ts

    line 98
  5. Very specific:

    NEVER: add an icon outside webview/src/design/icons.tsx

    line 100
  6. Very specific:

    NEVER: use the native title attribute — use design/primitives/Tooltip.

    line 101
  7. Very specific:

    NEVER: commit secrets, .env, or anything under ghost.one/

    line 104
  8. Very specific:

    NEVER: add a comment that explains _what_ code does → .claude/rules/comments.md

    line 106
  9. Distinctive:

    ALWAYS: run the gates before claiming done

    line 107
  10. Very specific:

    ALWAYS: treat theme.css, design/motion.ts and themes/_base.scss as shared resources — never edit them from parallel agents

    line 108
  11. Very specific:

    Only "instant" overrides CSS from script.

    line 132

≈ “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

LUNO for CC

A Claude Code chat panel for Cursor and VS Code, running on the user's own Claude subscription. Senior engineer on a shipped, published extension — not a prototype.

Language

  • Respond in Russian unless explicitly asked otherwise.
  • Code, comments, commit messages, docs — English.

Commands

The toolchain is bun. npm is not used: the lockfiles are bun.lock / webview/bun.lock and npm lockfiles are gitignored on purpose — if one reappears, something ran npm install and the two will drift.

TaskCommand
All gatesbun run lint (tsc ×2 → eslint → stylelint)
Types onlybun run lint:types
Testsbun run test — expect 1647 passed, 6 skipped
Buildbun run build (esbuild → dist/, vite → webview/dist/)
Packagebun run package → luno-for-cc-<ver>.vsix, ~1.28 MB
Formatbun run format · check with format:check
Autofix everythingbun run fix
Browser harnessbun run harness — the webview on localhost, see /browser
Webview dev serverbun run dev:webview → http://localhost:5173
Install both halvesbun run install:all · watch the host with bun run watch
Vendored skillsbun run skills:verify — fails if one was edited in place

bun run test is the number to beat, and it is the only place it is written down. The gate is "this many or better"; anything less is a regression to explain, not a floor to lower.

It runs two projects through vitest.workspace.ts and prints one summary: the host half in test/ under node, and a component half in webview/test/ under jsdom. The second is rooted in webview/ on purpose — React lives only in webview/node_modules, and a component rendered against a second copy of it fails on the first useState. Rendering uses react-dom/client and React 19's own act rather than a testing-library dependency; webview/test/render.tsx is the whole of it.

Architecture — three layers

Webview (webview/src/)   React 19 + SCSS modules + framer-motion
   │                     reads design tokens only
   │ postMessage — the stable contract between the two halves
Extension host (src/)    claude CLI spawn · permissions · MCP · skills
   │ spawn(binary, …) over stream-json
claude CLI               the user's own install; auth, models, tools
  • src/core/* imports zero VS Code APIs and is unit-tested in isolation.
  • The permission policy and CLI bridge live in src/providers/.
  • The binary is found, never assumed: everything goes through resolveClaudeBinary() in src/providers/factory.ts. Reaching for bundledClaudeBinary() directly breaks a build that ships no CLI, which is exactly what this is.

Tool triggers (MANDATORY)

Fire these without being asked. Each row is a trip-wire, not a suggestion.

SituationAction
Changed anything the webview rendersVerify in the browser harness (/browser) before reporting done. "Should work" is not a result
Need a version-specific API — React 19, framer-motion, VS Code, Vitecontext7 (resolve-library-id → query-docs). Never from memory: React 19 and framer's current API both moved recently
Unfamiliar library or third-party integrationExa first, code second
About to make an architectural decisionExa for how others solved it, then decide. Say what you found
Same error survived 2 fix attemptsStop guessing — systematic-debugging or Exa. A third blind attempt is always waste
Touched permissions, token storage, or process spawningRe-read decidePermission and the destructive/network gate. This extension holds a subscription credential and spawns processes
Measuring UI behaviour — duration, geometry, contrast, overshootSample it in the harness. Numbers, not adjectives
Context filling up mid-taskSay so and offer a handoff before quality drops, not after
Second opinion on a risky changecc-codex-triage

Definition of done

Never report work as complete without all four:

  1. bun run lint at exit 0, with no new warnings — that is tsc over both projects, eslint, stylelint. It is not a clean screen: 35 eslint warnings are real, held at warn on purpose, and explained in eslint.config.mjs. The gate is the exit code plus the count not rising
  2. bun run test at 1647 passed, 6 skipped or better
  3. Behaviour verified where it runs — the harness for UI, tests for host logic
  4. Every claim tied to evidence actually seen: a command's output, a measured value, a screenshot

If something is unverified, name which part and why. Partial honesty beats a confident false "готово".

Critical rules

  1. NEVER hard-code a colour, radius, shadow or duration in a component — read a token. See docs/TOKENS.md
  2. NEVER write a raw duration in framer — spread a preset from webview/src/design/motion.ts
  3. NEVER add an icon outside webview/src/design/icons.tsx
  4. NEVER use the native title attribute — use design/primitives/Tooltip. The one deliberate exception is the per-line title in FileDiffModal, which carries a comment saying so
  5. NEVER commit secrets, .env, or anything under ghost.one/
  6. NEVER let luno.permissionMode: auto reach a committed settings file
  7. NEVER add a comment that explains what code does → .claude/rules/comments.md
  8. ALWAYS run the gates before claiming done
  9. ALWAYS treat theme.css, design/motion.ts and themes/_base.scss as shared resources — never edit them from parallel agents

Traps this codebase has already paid for

Each of these was invisible to the compiler and cost a debugging round.

  • framer writes opacity inline, and no class rule can outrank it. Converting an element to motion.* silently kills any .someClass { opacity: … } on it. Use filter: opacity() for state-based dimming on animated elements.
  • CSS Modules scope @keyframes names. animation: globalName inside a module is a dead reference, and :global(name) inside an animation shorthand compiles to a silently dead one. Share via the mixins in themes/_motion.scss — in the source, not the output.
  • CSS cannot animate an exit. Anything that must animate both ways needs framer + AnimatePresence; a CSS animation plays on mount and has nothing left to play when the node is simply gone.
  • Exits run on --ease-soft, never --ease-out. The latter is an expo-out: ~90% of the change lands in the first third, so a dismissed panel is invisible halfway through its own duration.
  • scrollTo({behavior:"auto"}) does not mean "jump". It means "use the element's scroll-behavior", and .log in ChatScreen.module.scss sets that to smooth — so auto silently keeps the animation you were trying to skip. Only "instant" overrides CSS from script. Cost: two full rebuild-and-measure cycles reading an unchanged 1 523 ms.
  • A dangling s.someName renders unstyled and the build stays green. A typo in a CSS-module class name is not a compile error.
  • prefers-reduced-motion: framer honours it only on the animate-prop path. A value driven through useSpring bypasses it and needs an explicit useReducedMotion() check.
  • A disabled button dispatches no mouse events, and they do not reach its ancestors — hit-testing stops there. That is why Tooltip has a gate span.
  • window.open and <a target="_blank"> do not reach the browser from a webview. Every outbound link goes send({ type: "openExternal", url }) and the host calls vscode.env.openExternal. The failure is silent: the click lands, nothing happens.
  • Every field on the protocol is optional, so a half-plumbed one typechecks. A value crosses five hops — card → ChatScreen → App → host → provider — and adding it to four of them compiles clean and drops the value on the floor. When adding a field, grep the name and count the hops.
  • A native radio or checkbox cannot be made to match the design system. accent-color tints the control but the platform still draws a square focus ring around a round radio. Hide the input (visually, never display: none — it carries the semantics and the tab order) and draw a sibling.

Where the rest lives

TopicFile
Token contract every theme must satisfydocs/TOKENS.md
What a file becomes when it is attacheddocs/ATTACHMENTS.md
Comment policy in full.claude/rules/comments.md
Naming and code style.claude/rules/code-style.md
What lives where, sizes, seams.claude/project-map.md
Why a decision went that waydocs/PLAN.md (local only)
Design rationaledocs/DESIGN.md (local only)
Audits — measured findings, with proof.claude/audits/

docs/PLAN.md and docs/DESIGN.md are gitignored — working notes, not part of the published repo. Read them for context; do not assume a reader of the repository has them.

Development environment

  • OS: Windows. Shell: PowerShell primary, Git Bash available.
  • Paths: forward slashes work in both. Line endings: CRLF in the working tree, LF in git (core.autocrlf).
  • ghost.one/ is a full copy of an unrelated project kept in-tree as a reference. It is gitignored and .vscodeignored. Never commit it, never let vsce walk it.

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.