RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/Cursor rules/nerds-odd-e/doughnut

Cursor rule

.cursor/rules/cli.mdc

related to doughnut CLI

Cursor rules

Quality

96/100

Scores the file, not the repository.

Length

1,812 words

12 headings · 1 code blocks

Repository

49

— · pushed 0 days ago

Last changed

3 days ago

First indexed 3 days ago.
nerds-odd-e/doughnut/.cursor/rules/cli.mdcRawGitHub
1---
2description: related to doughnut CLI
3globs: cli/**, e2e_test/features/cli/**
4alwaysApply: false
5---
6# doughnut-cli
7 
8TypeScript CLI for Doughnut. Lives in `cli/`.
9 
10## Structure
11 
12```
13cli/
14 src/ # Production code: `main`/`run`, `nonInteractiveCli`, `interactiveInkSession` (TTY → Ink edge), Ink app, `commands/`
15 src/commonUIComponents/ # Context-neutral Ink UI reused across stages and the main prompt (borders, guidance lists, y/n, past user block, stage key context)
16 src/sessionScrollback/ # One Ink `<Static>` session history above the live column; transcript factories + recall answered rows; append context for stages
17 src/commands/ # Slash-command and subcommand implementations + aggregated help
18 src/shims/ # Modules referenced only from the esbuild `bundle` script (aliases)
19 tests/ # Vitest unit tests (*.test.ts)
20 vitest.config.ts
21 tsconfig.json
22 package.json
23```
24 
25## TypeScript module exports
26 
27Keep each module’s **public surface small**: `export` only what other modules actually use. Prefer leaving helpers, constants, and types **unexported** when they are implementation details. Do not add exports “for tests” or “maybe later” — if only tests need a symbol, test through a higher-level entry point when possible (see **Vitest: observable behavior** below). Avoid widening the export list when a single import site could instead live next to the code.
28 
29## Commands
30 
31| Task | Command |
32|------|---------|
33| Build bundle | `pnpm cli:bundle` |
34| Run tests | `pnpm cli:test` |
35| Mutation test (Stryker) | `CURSOR_DEV=true nix develop -c bash -c 'cd cli && pnpm test:mutation'` — see the `mutation-testing` skill |
36| Format | `pnpm cli:format` |
37| Lint | `pnpm cli:lint` |
38 
39## Architecture Roadmap
40 
41The architecture roadmap is at `ongoing/cli-architecture-roadmap.md` (legacy location; leave in place). New planning artifacts go under `.planning/phases/` or `.planning/quick/` — see `gsd-coexistence.mdc`.
42 
43It is a guideline for growing the architecture, not a direct implementation checklist. Apply it when a feature needs it; challenge the fit first. Update it as decisions land and refine the forward-looking parts as understanding changes.
44 
45**Session scrollback (interactive)** — Past session lines use **one** Ink `<Static>` (append-only) above the live column (stage + `MainInteractivePrompt`). Generic `SessionScrollback` stays domain-agnostic; shell transcript shapes live in `interactiveCliTranscript.tsx`; recall “answered” outcomes use `recallAnsweredScrollback.tsx`; stages append via `sessionScrollbackAppendContext`.
46 
47**Interactive TTY boundary** — `interactiveInkSession.ts` only checks TTY, prints the welcome banner, and calls Ink `render` with injectable `stdin`/`stdout`. Domain behavior stays in `InteractiveCliApp` and `commands/`.
48 
49**User-visible slash-command errors** — Map failures to assistant text with `userVisibleSlashCommandError` (see `cli/tests/userVisibleSlashCommandError.test.ts`). Red past-assistant blocks use `pastAssistantErrorBlock.tsx` for committed transcript lines.
50 
51**Stage keyboard routing** — `SetStageKeyHandlerContext` (`stageKeyForwardContext.tsx`): the shell registers one handler so Esc and other stage keys are handled without competing Ink `useInput` instances; stages that need it install via context (e.g. `AsyncAssistantFetchStage`).
52 
53## Terminal column width (TTY layout)
54 
55**Do not use** JavaScript string `.length` or UTF-16 code units to measure how wide text is on screen. Terminals use **column count**: CJK and many emoji render as **2 columns**; grapheme clusters (flags, ZWJ families, text + VS16) must be measured as units.
56 
57## Vitest: observable behavior
58 
59For **interactive** behavior, prefer **`runInteractive`** (from `interactive.js`, implemented in `interactiveInkSession.ts`) with a **mock TTY** stdin and assert **stdout** / visible output — the test may **not import** the module you changed; coverage through the CLI surface is enough. For **argv routing** (`version`, `help`, interactive fallback), use **`run`** from `run.js` (see `cli/tests/index.test.ts`). For cross-cutting test strategy, see **Observable behavior first** in the `phased-planning` skill.
60 
61**Mocking Doughnut HTTP from unit tests** — Use **`vi.spyOn`** on **`doughnut-api`** controller static methods (e.g. `RecallsController.recalling`, `MemoryTrackerController.showMemoryTracker`) and **`mockResolvedValue`** with the SDK success shape (`{ data: … }`, cast as **`Awaited<ReturnType<typeof Controller.method>>`** when needed). Build **`data`** values that match backend / SDK types with **`makeMe`** from **`doughnut-test-fixtures/makeMe`** (e.g. `makeMe.aMemoryTracker`, `makeMe.aNoteRealm`, `makeMe.aDueMemoryTrackersList`) instead of ad hoc object literals. Do **not** use **`http.createServer`** to fake `/api/…` for ordinary command behavior. Reserve a real local HTTP server for tests whose subject is transport or error classification (e.g. status codes), not for happy-path recall or token flows.
62 
63**No fixed-time waits in unit tests** — Do not use `sleep`, `setTimeout(…, N)` with a duration, or similar wall-clock delays to “let Ink/React catch up.” Prefer driving the real async surface: `setImmediate` / microtask turns in a loop until an **observable** condition holds (e.g. `frames` or stdout contains the expected text), with a **turn-count** cap and a clear failure message if the condition never becomes true. E2E may still use bounded retries where appropriate; Vitest unit tests under `cli/tests/` should stay deterministic without arbitrary milliseconds.
64 
65## Ink + React + Node (avoid flaky interactive tests)
66 
67- **Defer `useApp().exit()` / unmount after UI updates:** Do not call **`exit()`** from the same synchronous turn as a slash command that still has to append transcript state (e.g. “Bye.”). **`/exit`** is special-cased in **`InteractiveCliApp`**: after the assistant line is committed, a **`useEffect`** runs **`exit()`**. In Node, **`setTimeout(…, 0)`** can still run **before** **`setImmediate`** work used by React/Ink — avoid **`setTimeout(…, 0)`** for this ordering.
68- **Stable `useInput` handler:** Pass **`useCallback`** (with correct deps) to **`useInput`**, not a new inline function every render. Ink’s `useInput` effect depends on the handler reference; a new function each render tears down and re-attaches the internal listener and can drop keystrokes under load.
69- **`ink-testing-library` + stdin:** **`useInput` registers via `useEffect`** — **`stdin.write` immediately after `render()` can race** empty listeners. Before real input, **wait on an observable** (e.g. write a harmless probe key, **`waitForFrames` until `lastFrame()` shows it**, then undo if needed), or use **`renderInkWhenCommandLineReady`** from `cli/tests/inkTestHelpers.ts` (probe key + wait; see `InteractiveCliApp` ink tests).
70- **Ink test async helpers:** Import **`waitForFrames`**, **`waitForLastFrame`**, and **`stripAnsi`** from **`cli/tests/inkTestHelpers.ts`** instead of duplicating the `setImmediate` poll loop (or ANSI stripping) in each test file. After **`renderInkWhenCommandLineReady`**, prefer **`lastStrippedFrame()`** (current frame, ANSI-stripped), **`waitForLastFrameToInclude(pattern)`**, and **`waitForFramesToInclude(pattern)`** where **`pattern`** is a substring or **`RegExp`** (combined scrollback is ANSI-stripped for matching). Use **`waitForFrames`** / raw **`frames.join('\n')`** when the assertion must see **SGR sequences** (e.g. `\x1b[100m`) that stripping would remove.
71- **`<Static>` scrollback:** Session history is rendered inside Ink `<Static>`; captured **`frames`** can **repeat** the same scrollback text every frame. For “appears once on screen” or row budgets, prefer **`lastFrame()`** / **`lastStrippedFrame()`**, not counting substrings across **`frames.join('\n')`**.
72- **Typing simulations:** Do not use **`setImmediate` per character** as “Ink is ready.” **Wait until the frame shows the expected buffer** (or combined `frames` text) before the next `stdin.write`.
73 
74## Domain terminology
75 
76Vocabulary for Cucumber steps and page objects (`e2e_test/start/pageObjects/cli/`). **Exact TTY behavior** (past messages, user input history, cursor, rendering) lives in code + Vitest; **scenario-shaped coverage** in `e2e_test/features/cli/*.feature`.
77 
78| Term | Definition |
79|------|-------------|
80| **Non-interactive output** | Full stdout for E2E subcommand spawns (e.g. installed `version` / `update`) and similar one-shot runs; no PTY interactive input-ready control sequence. |
81| **Past CLI assistant messages** | Interactive: past CLI output blocks in the session scrollback (shell assistant lines, errors, session summaries, and recall **answered** lines such as `Correct!` / `Reviewed:` — the latter as `RecallAnsweredItem`, not duplicate `onSettled` assistant text). Gherkin: `in past CLI assistant messages`. |
82| **Past user messages** | Interactive: past user lines as gray-background blocks (`\x1b[100m`…), one blank padded row above the text (E2E checks this); one padded row below before the command line (see Vitest `pastUserMessageBlock` / `InteractiveCliApp.test`). Gherkin: `in past user messages`. Recall **y/n** confirmations (stop recall, load more, just-review; prompt footer may show `(y/n)` or `(Y/n)` / `(y/N)` when Enter commits a default) do **not** add a separate past user message row — only the outcome lines appear. On **Load more from next 3 days?**, **Esc** declines load more (same outcome as **n**, session summary), not the card-level leave-recall confirm. |
83| **User input history** | TTY: committed lines for ↑↓ recall + persistence (`mainInteractivePrompt/history.ts`, shared store `inputHistory/`) **while the command-line Ink region has focus**. The live command buffer is **single-line** (newlines from paste become spaces; no Shift+Enter newline). Masked before storage/display. Recall **y/n** answers are not appended (same rule as past user messages). |
84| **Current Stage** | Conceptual state during a multi-step or long-running command (e.g. recall session, slow interactive network call). |
85| **Current Stage Indicator** | On the TTY, when a stage is surfaced: the first line of the **Current prompt** block — full terminal width on the **Current stage band** (e.g. “Recalling” while in recall). Not part of **Current guidance**. |
86| **Current stage band** | Shared background for the Current Stage Indicator line and, when the indicator is shown, the **Current prompt** separator under it, so the top of the block reads as one strip. Implemented as `CURRENT_STAGE_BAND_BACKGROUND_SGR` in `cli/src/renderer.ts`. |
87| **Current prompt** | Block above the **command line** (live typing strip): optional **Current Stage Indicator** + separator (banded when the indicator is shown), then wrapped lines (MCQ stem and notebook line, fetch-wait prompt, y/n text, token-list copy, etc.). Recall **MCQ** (TTY): **numbered choices** live in **Current guidance**, not here. |
88| **Current guidance** | Below the command line: `/` hints, token lists, **MCQ numbered choices** (wrapped to terminal width; ↑↓ selects by choice index). |
89 
90## CLI E2E
91 
92Features: **`e2e_test/features/cli/`**. Steps: **`e2e_test/step_definitions/cli.ts`** (thin glue only). Page objects and terminal assertions: **`e2e_test/start/pageObjects/cli/`**, especially **`outputAssertions.ts`** — put new “what appears in the terminal?” checks there (retries, ANSI-stripped snapshot text on failure, screenshot on the final throw path).
93 
94- **Run:** Cypress Node tasks spawn `cli/dist/doughnut-cli.bundle.mjs` via `node` (same locally and in GitHub Actions). Before spawn, `ensureCliBundleFresh` rebuilds the bundle when `cli/src`, `cli/package.json`, `cli/tsconfig.json`, or `packages/doughnut-api/src` are newer than the bundle. Set **`DOUGHNUT_CLI_E2E_USE_TSX=1`** to force `pnpm -C cli exec tsx src/index.ts` for debugging. Installation scenarios use the E2E install bundle path (see `@bundleCliE2eInstall`).
95- **`@bundleCliE2eInstall`:** Builds `cli/dist/e2e-install-doughnut-cli.bundle.mjs` before each scenario and removes it after; the local LB (`scripts/local-lb.mjs`) serves `/doughnut-cli-latest/doughnut` from that file when present so install tests do not overwrite `cli/dist/doughnut-cli.bundle.mjs`.
96- **Active CLI E2E (CI):** `e2e_test/features/cli/cli_install_and_run.feature` non-ignored scenarios only. **`installCli`** runs the install script. Non-interactive steps use **`runInstalledCli`** (`node <installed binary> …` in a **managed PTY**, same geometry and env merge as interactive; waits for exit code 0) and **`cli.nonInteractiveOutput().expectContains`** → **`cliAssert`** with **`strippedTranscript`** (`nonInteractiveCliOutputAssertRequest` in `outputAssertions.ts`). The **Install and run the CLI in interactive mode** scenario uses **`runInstalledCliInteractive`**, **`cliInteractiveWriteLine`** for slash input, and transcript assertions **`interactiveCli().pastCliAssistantMessages().expectContains`** / **`pastUserMessages().expectDisplayed`** (two **`cliAssert`** requests: full-buffer gray-block rules, then stripped-transcript blank-line-above). Assertions run in the plugin via **`cliAssert`** → `tty-assert` managed session, not browser-side buffer polling. On assert failure, the plugin saves a **viewport PNG** and, when **`tty-assert`** has recorded at least two distinct viewport frames, an animated **GIF** (`buildViewportAnimationGif`), under the current spec folder via **`saveBufferToCurrentSpecFolder`** (`e2e_test/config/cliE2ePluginTasks.ts`, `cypressSpecScreenshotSink.ts`).
97 
98## Build output
99 
100- Bundle: `cli/dist/doughnut-cli.bundle.mjs` (shebang)
101- Release: `gs://dough-frontend-01/doughnut-cli-latest/doughnut` (`cli-release.yml`)
102- Local install URL: local LB serves `/doughnut-cli-latest/doughnut` (see `scripts/local-lb.mjs`, `docs/gcp/prod_env.md`)
103 
104**Ink + esbuild (`react-devtools-core`):** Ink may load `devtools.js`, which imports `react-devtools-core`. That package is optional in Ink (used when `DEV=true`; see Ink’s README). Esbuild still resolves the import when producing the single-file bundle, so `cli/package.json` `bundle` aliases `react-devtools-core` to `cli/src/shims/react-devtools-core-stub.ts`. The shipped bundle therefore does not depend on installing `react-devtools-core`. For React DevTools against an **unbundled** run (e.g. `pnpm -C cli exec tsx src/index.ts`), install `react-devtools-core` and use `DEV=true` as Ink documents.
105 
106## Install scripts
107 
108- Bash: `backend/src/main/resources/install.sh`
109- PowerShell: `backend/src/main/resources/install.ps1`
110- Served at `/install` (`InstallController`; `?win32=true` for PowerShell)
111 

Commands it names

  • vitest.config.ts
  • bundle
  • pnpm cli:bundle
  • pnpm cli:test
  • pnpm cli:format
  • pnpm cli:lint
  • node
  • pnpm -C cli exec tsx src/index.ts
  • node <installed binary> …

Sections

  • doughnut-cli
  • Structure
  • TypeScript module exports
  • Commands
  • Architecture Roadmap
  • Terminal column width (TTY layout)
  • Vitest: observable behavior
  • Ink + React + Node (avoid flaky interactive tests)
  • Domain terminology
  • CLI E2E
  • Build output
  • Install scripts

What it covers

setupbuildtestcode-stylearchitecturetypestesting-strategydo-not

Stack — with the evidence

typescript

(1.00)

cypress

(1.00)

biome

(1.00)

node

(0.95)

vitest

(0.95)

pnpm

(0.85)

react

(0.70)

vue

(0.70)

hono

(0.70)

tailwind

(0.70)

vite

(0.70)

pytest

(0.70)

javascript

(0.60)

python

(0.60)

monorepo

(0.60)

github-actions

(0.60)

Glob targeting

  • cli/**
  • e2e_test/features/cli/**

Format

Cursor rules

The most expressive format here. Many small .mdc files, each with frontmatter declaring when it should load, so a rule about migrations only enters context when a migration is open. Costs the most to maintain and only one editor reads it.

What the corpus says about it

Repository

Owner
nerds-odd-e
Language
—
License
—
Archived
no

All configs in this repo

Also in nerds-odd-e/doughnut

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
nerds-odd-e/doughnut.clinerules/daisyui.md · 49Cline rulestypescriptcypress+14setuplint-formatstyleui+157/1003 days ago
nerds-odd-e/doughnut.cursor/rules/architecture-decisions.mdc · 49Cursor rulestypescriptcypress+14no sections16/1003 days ago
nerds-odd-e/doughnut.cursor/rules/backend-code.mdc · 49Cursor rulestypescriptcypress+14styletypesdatabasedo-not61/1003 days ago
nerds-odd-e/doughnut.cursor/rules/backend-testing.mdc · 49Cursor rulestypescriptcypress+14buildteststyletesting-strategy+273/1003 days ago
nerds-odd-e/doughnut.cursor/rules/db-migration.mdc · 49Cursor rulestypescriptcypress+14stylearchdatabasedeployment64/1003 days ago
nerds-odd-e/doughnut.cursor/rules/e2e-authoring.mdc · 49Cursor rulestypescriptcypress+14setupteststylearch+380/1003 days ago
nerds-odd-e/doughnut.cursor/rules/e2e-ocr.mdc · 49Cursor rulestypescriptcypress+14setuptesting-strategydo-not46/1003 days ago
nerds-odd-e/doughnut.cursor/rules/frontend-api.mdc · 49Cursor rulestypescriptcypress+14styletesting-strategyapido-not57/1003 days ago
nerds-odd-e/doughnut.cursor/rules/frontend-component.mdc · 49Cursor rulestypescriptcypress+14testlint-formatstylearch+276/1003 days ago
nerds-odd-e/doughnut.cursor/rules/frontend-storybook.mdc · 49Cursor rulestypescriptcypress+14buildteststyletesting-strategy+169/1003 days ago
nerds-odd-e/doughnut.cursor/rules/frontend-testing.mdc · 49Cursor rulestypescriptcypress+14buildteststyletesting-strategy+289/1003 days ago
nerds-odd-e/doughnut.cursor/rules/general.mdc · 49Cursor rulestypescriptcypress+14styledo-not49/1003 days ago
nerds-odd-e/doughnut.cursor/rules/gsd-coexistence.mdc · 49Cursor rulestypescriptcypress+14style60/1003 days ago
nerds-odd-e/doughnut.cursor/rules/linting_formating.mdc · 49Cursor rulestypescriptmonorepo+14testlint-formatstylearch+688/1003 days ago
nerds-odd-e/doughnut.cursor/rules/mcp-server.mdc · 49Cursor rulestypescriptcypress+14buildtestlint-formatarch+285/1003 days ago
nerds-odd-e/doughnut.cursor/rules/planning.mdc · 49Cursor rulestypescriptcypress+14teststylearchdo-not+175/1003 days ago
nerds-odd-e/doughnut.cursor/rules/script.mdc · 49Cursor rulestypescriptcypress+14testarch58/1003 days ago
nerds-odd-e/doughnutAGENTS.md · 49AGENTS.mdtypescriptcypress+14no sections47/1003 days ago
nerds-odd-e/doughnutCLAUDE.md · 49CLAUDE.mdtypescriptcypress+14agent-behaviour47/1003 days ago
Diff against .clinerules/daisyui.md Diff against .cursor/rules/architecture-decisions.mdc Diff against .cursor/rules/backend-code.mdc Diff against .cursor/rules/backend-testing.mdc Diff against .cursor/rules/db-migration.mdc Diff against .cursor/rules/e2e-authoring.mdc Diff against .cursor/rules/e2e-ocr.mdc Diff against .cursor/rules/frontend-api.mdc Diff against .cursor/rules/frontend-component.mdc Diff against .cursor/rules/frontend-storybook.mdc Diff against .cursor/rules/frontend-testing.mdc Diff against .cursor/rules/general.mdc Diff against .cursor/rules/gsd-coexistence.mdc Diff against .cursor/rules/linting_formating.mdc Diff against .cursor/rules/mcp-server.mdc Diff against .cursor/rules/planning.mdc Diff against .cursor/rules/script.mdc Diff against AGENTS.md Diff against CLAUDE.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126Cursor rulesgobun+5setupbuildtestlint-format+6100/1003 days ago
TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45Cursor rulestypescriptpytest+15testlint-formatstylearch+5100/1003 days ago
dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+15setuptestlint-formatstyle+799/1003 days ago
deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1Cursor rulestypescriptnextjs+5setuptestlint-formatstyle+799/1003 days ago
markstev/mark-starter.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+14setuptestlint-formatstyle+699/1003 days ago
Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0Cursor rulestypescriptturborepo+13setuptestlint-formatstyle+799/1003 days ago
langflow-ai/langflow.cursor/rules/docs_development.mdc · 153kCursor rulespythonnode+16setupbuildtestlint-format+797/1003 days ago
TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45Cursor rulestypescriptpytest+15teststyletesting-strategysecurity+397/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