Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .claude/skills/add-command/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Before writing any code, understand what the command should do and whether the a
**Core questions:**
1. **What does this command do?** (one sentence — e.g., "shows the status of running emulators")
2. **Does it need to talk to Docker/the runtime?** (determines whether `runtime.Runtime` is a dependency)
3. **Does it need configuration?** (determines whether `PreRunE: initConfig` is needed)
3. **Does it need configuration?** (determines whether `PreRunE: initConfig(nil)` is needed)
4. **Does it need authentication?** (determines whether auth flow is involved)
5. **Does it need any new event types?** (e.g., a new kind of progress, a new status phase — if yes, use `/add-event` for each)

Expand All @@ -42,10 +42,11 @@ Read these files before writing anything — they are the source of truth for pa
Create `cmd/$ARGUMENTS.go` with:

- A `new<Name>Cmd()` factory function returning `*cobra.Command`
- `PreRunE: initConfig` if the command needs configuration
- Output mode decision at the boundary:
- `PreRunE: initConfig(nil)` if the command needs configuration (only the root command uses `initConfigDeferCreate(&firstRun)`)
- Output mode decision at the boundary, gated on `isInteractiveMode(cfg)` (covers non-TTY and the `--non-interactive` flag):
- Interactive: delegate to `ui.Run<Name>(...)` or TUI path
- Non-interactive: call domain function with `output.NewPlainSink(os.Stdout)`
- If the command only groups subcommands (like `config`, `setup`, `snapshot`), call `requireSubcommand(cmd)` so a bare invocation shows help (exit 0) while an unknown subcommand exits non-zero
- No business logic — only Cobra wiring, dependency creation, and output mode selection
- `Short`/`Long` help text: write each paragraph as one unbroken line (blank line between paragraphs); never hard-wrap a sentence across source lines. The help template (`cmd/help.go`) word-wraps to the terminal width at render time, and `lstk docs` reads the raw text — manual breaks fight both. Indent example/output blocks; the wrapper leaves indented lines untouched.

Expand All @@ -54,13 +55,13 @@ Create `cmd/$ARGUMENTS.go` with:
Create `internal/<package>/<name>.go` (use an existing package if it fits, or create a new one) with:

- A function that accepts `ctx context.Context`, `rt runtime.Runtime`, `sink output.Sink`, and any other dependencies
- Emit events via `output.EmitXxx(sink, ...)` — never `fmt.Print` or `log.Print`
- Return errors normally; use `output.NewSilentError(err)` only if the error was already displayed via `EmitError`
- Emit events via `sink.Emit(output.XxxEvent{...})` — never `fmt.Print` or `log.Print`, and never add package-level emit helpers
- Return errors normally; use `output.NewSilentError(err)` only if the error was already displayed via `sink.Emit(output.ErrorEvent{...})`
- No imports from `internal/ui` or `charmbracelet/bubbletea`

## Step 3: Register the command

In `cmd/root.go`, add the new command to `root.AddCommand(...)`.
In `cmd/root.go`, add the new command to the appropriate group slice in `NewRootCmd`: the `commands` slice (core commands, `GroupID = groupCommands`) or the `tools` slice (proxy commands, `GroupID = groupTools`). A command appended via a bare `root.AddCommand(...)` with no `GroupID` lands under "Additional Commands" in help — almost never what you want.

If the command constructor needs dependencies (like `*env.Env`), add them as parameters matching the existing pattern.

Expand Down Expand Up @@ -91,6 +92,7 @@ Create `test/integration/<name>_test.go` with:

- Non-interactive tests: `exec.CommandContext(ctx, binaryPath(), "<name>")` → `cmd.CombinedOutput()`
- Interactive (TUI) tests: use `pty.Start(cmd)` from `github.com/creack/pty`
- Never inherit the developer's real `$HOME`: pass `testEnvWithHome(t.TempDir(), "")` (or extend it with `env.With(...)`) as the command env — never `nil` or `os.Environ()`
- Use `requireDocker(t)` if Docker is needed
- Use `cleanup()` and `t.Cleanup(cleanup)` for container state
- Use `context.WithTimeout` for all tests
Expand All @@ -105,7 +107,7 @@ In the corresponding integration test, add an assertion that the `lstk_command`

- Do NOT put business logic in `cmd/` — the command file should be thin wiring only
- Do NOT construct sinks inside domain code — always accept `output.Sink` as a parameter
- Do NOT use `fmt.Print`/`log.Print` in domain code — use `output.EmitXxx()` helpers
- Do NOT use `fmt.Print`/`log.Print` in domain code — emit events on the injected `output.Sink`
- Do NOT import `internal/ui` or Bubble Tea from domain packages
- Do NOT create package-level global variables — inject dependencies via constructors
- Do NOT use "container" or "runtime" in user-facing text — use "emulator"
2 changes: 1 addition & 1 deletion .claude/skills/add-component/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ var (
)
```

Use existing palette constants (`NimboDarkColor`, `NimboMidColor`, `NimboLightColor`) or standard ANSI color codes. Name styles after what they represent, not how they look.
Use existing palette constants (`NimboDarkColor`, `NimboMidColor`, `NimboLightColor`, `SuccessColor`) or standard ANSI color codes. Name styles after what they represent, not how they look.

## Step 3: Wire into App

Expand Down
6 changes: 4 additions & 2 deletions .claude/skills/create-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: create-pr
description: Create a GitHub pull request following lstk conventions with proper title, description, and ticket references.
argument-hint: [base-branch]
disable-model-invocation: true
allowed-tools: Bash(git log *), Bash(git diff *), Bash(git branch *), Bash(git push -u origin HEAD), Bash(gh pr create *), Bash(gh label list *), mcp__plugin_linear_linear__get_issue
allowed-tools: Bash(git log *), Bash(git diff *), Bash(git branch *), Bash(git push -u origin HEAD), Bash(gh pr create *), Bash(gh label list *), mcp__claude_ai_Linear__get_issue
---

# Create Pull Request
Expand All @@ -27,7 +27,9 @@ If `$ARGUMENTS` is provided, use it as the base branch instead of `main`.

Extract the ticket ID from the branch name. The ticket ID is the last path segment, uppercased (e.g., branch `user/abc-123` → ticket `ABC-123`).

Use the Linear MCP tool (`mcp__plugin_linear_linear__get_issue`) to fetch the ticket details — title and description. Use this context to inform the PR motivation.
Use the Linear MCP tool (`mcp__claude_ai_Linear__get_issue`) to fetch the ticket details — title and description. Use this context to inform the PR motivation.

If the branch name carries no ticket ID, ask whether a Linear issue exists (issues live in Linear, not GitHub Issues). When one exists, prefer its Linear-generated branch name for future work; when none exists and the change warrants tracking, offer to create the issue first and reference it.

## Step 3: Write the PR title

Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/review-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Go through each changed file and check for violations. Flag only actual problems

### Output and event system

- [ ] No direct `fmt.Print`/`log.Print` in domain code — uses `output.EmitXxx()` helpers instead
- [ ] No direct `fmt.Print`/`log.Print` in domain code — emits events via `sink.Emit(output.XxxEvent{...})` instead (no package-level emit helpers)
- [ ] New event types (if any) are added to all required locations:
- `internal/output/events.go` (struct + `Event` union + emit helper)
- `internal/output/events.go` (struct + `sealedEvent()` marker)
- `internal/output/plain_format.go` (`FormatEventLine` case)
- Tests in `internal/output/*_test.go`
- [ ] Event payloads carry domain facts, not pre-rendered UI strings
Expand Down
Loading
Loading