litclaude-ai 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,328 @@
1
+ # Bootstrap — Project Layout, Toolchain, Taskfile, CI
2
+
3
+ What every new Go project gets in the first 60 seconds. Drop the script in `scripts/go/new-project.go` does all of this — this document explains *what* it produces and *why*.
4
+
5
+ ## Toolchain pin
6
+
7
+ `go.work` (monorepo) or just rely on `go.mod`'s `go 1.23` directive (single module). Go 1.21+ auto-downloads matching toolchain when the local `go` binary is older. **No `.tool-versions` / `asdf` / `mise` indirection required** unless your shop standardizes on it.
8
+
9
+ ```bash
10
+ # Confirm a working toolchain
11
+ go env GOTOOLCHAIN # should be "auto" or your pinned version
12
+ go version # ≥ 1.23
13
+ ```
14
+
15
+ ## Required global installs
16
+
17
+ These are CLI tools, installed once per machine via `go install`:
18
+
19
+ ```bash
20
+ go install mvdan.cc/gofumpt@latest
21
+ go install golang.org/x/tools/cmd/goimports@latest
22
+ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.0.0
23
+ go install go.uber.org/nilaway/cmd/nilaway@latest
24
+ go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
25
+ go install github.com/pressly/goose/v3/cmd/goose@latest
26
+ go install go.uber.org/mock/mockgen@latest
27
+ go install github.com/go-task/task/v3/cmd/task@latest
28
+ ```
29
+
30
+ For Connect/protobuf projects, additionally:
31
+
32
+ ```bash
33
+ go install github.com/bufbuild/buf/cmd/buf@latest
34
+ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
35
+ go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
36
+ ```
37
+
38
+ ## Project layout — the canonical tree
39
+
40
+ ```
41
+ myservice/
42
+ ├── go.mod
43
+ ├── go.sum
44
+ ├── Taskfile.yml # task runner
45
+ ├── .golangci.yml # see golangci-strict.md
46
+ ├── .editorconfig
47
+ ├── .gitignore
48
+ ├── README.md
49
+ ├── AGENTS.md # agent-readable project facts
50
+ ├── cmd/
51
+ │ └── server/
52
+ │ └── main.go # ONLY: parse flags, call cmd.Execute(); ≤ 50 LOC
53
+ ├── internal/ # NEVER importable from outside this module
54
+ │ ├── api/ # transport layer (gin/connect routers)
55
+ │ │ ├── server.go # gin engine setup, route registration
56
+ │ │ ├── middleware/
57
+ │ │ │ ├── request_id.go
58
+ │ │ │ ├── logging.go
59
+ │ │ │ └── auth.go
60
+ │ │ └── handlers/
61
+ │ │ ├── users.go
62
+ │ │ └── users_test.go
63
+ │ ├── domain/ # parse-don't-validate types, smart constructors
64
+ │ │ ├── user.go
65
+ │ │ └── email.go
66
+ │ ├── service/ # business logic, depends on domain only
67
+ │ │ └── user_service.go
68
+ │ ├── store/ # persistence; sqlc-generated code lives here
69
+ │ │ ├── sqlc/ # sqlc-generated, do not hand-edit
70
+ │ │ ├── queries/ # *.sql files sqlc reads
71
+ │ │ └── migrations/ # goose migrations
72
+ │ ├── config/ # env-driven config (caarlos0/env)
73
+ │ │ └── config.go
74
+ │ └── obs/ # observability: slog setup, otel, healthz
75
+ │ └── logger.go
76
+ ├── pkg/ # exportable libraries — only if you publish
77
+ │ └── …
78
+ ├── proto/ # *.proto definitions (Connect/gRPC projects)
79
+ │ └── service.proto
80
+ ├── gen/ # generated code (Connect, OpenAPI)
81
+ │ └── service/v1/
82
+ │ ├── service.pb.go
83
+ │ └── servicev1connect/
84
+ ├── test/ # cross-cutting test helpers, fixtures
85
+ └── .github/workflows/ci.yml
86
+ ```
87
+
88
+ **Rules**:
89
+
90
+ - `cmd/<binary>/main.go` is ≤ 50 LOC. Anything more lives in `internal/cmd/`.
91
+ - `internal/` is **the** business code. Other modules cannot import it (Go compiler-enforced).
92
+ - `pkg/` is for things you genuinely want third parties to import. Empty until proven otherwise.
93
+ - No `utils/`, `helpers/`, `common/`, `shared/`. **REJECT.** Files are named after the concept they own.
94
+ - One package per directory. One responsibility per package.
95
+
96
+ ## `Taskfile.yml` — the entry point for every action
97
+
98
+ `go-task/task` is the modern Make replacement. Cross-platform, YAML, fast.
99
+
100
+ ```yaml
101
+ version: '3'
102
+
103
+ vars:
104
+ BINARY: server
105
+ PKG: ./cmd/server
106
+
107
+ tasks:
108
+ default:
109
+ deps: [fmt, lint, test]
110
+
111
+ fmt:
112
+ desc: Format all Go files
113
+ cmds:
114
+ - gofumpt -w .
115
+ - goimports -w -local "$(go list -m)" .
116
+
117
+ lint:
118
+ desc: Run all linters
119
+ cmds:
120
+ - golangci-lint run --timeout 5m ./...
121
+ - nilaway -include-pkgs "$(go list -m)/..." ./...
122
+
123
+ test:
124
+ desc: Run tests with race detector
125
+ cmds:
126
+ - go test -race -shuffle=on -count=1 ./...
127
+
128
+ test-cover:
129
+ desc: Coverage report
130
+ cmds:
131
+ - go test -race -shuffle=on -count=1 -coverprofile=coverage.out ./...
132
+ - go tool cover -html=coverage.out -o coverage.html
133
+
134
+ build:
135
+ desc: Build the binary
136
+ cmds:
137
+ - go build -trimpath -ldflags="-s -w" -o bin/{{.BINARY}} {{.PKG}}
138
+
139
+ run:
140
+ desc: Run the server locally
141
+ deps: [build]
142
+ cmds:
143
+ - ./bin/{{.BINARY}}
144
+
145
+ gen:
146
+ desc: Run all code generators
147
+ cmds:
148
+ - task: gen:sqlc
149
+ - task: gen:mocks
150
+ - task: gen:proto
151
+
152
+ gen:sqlc:
153
+ cmds:
154
+ - sqlc generate
155
+ sources:
156
+ - internal/store/queries/*.sql
157
+ - internal/store/sqlc.yaml
158
+ generates:
159
+ - internal/store/sqlc/*.go
160
+
161
+ gen:mocks:
162
+ cmds:
163
+ - go generate ./...
164
+
165
+ gen:proto:
166
+ cmds:
167
+ - buf generate
168
+ sources:
169
+ - proto/**/*.proto
170
+ - buf.yaml
171
+ - buf.gen.yaml
172
+
173
+ migrate:up:
174
+ cmds:
175
+ - goose -dir internal/store/migrations postgres "$DATABASE_URL" up
176
+
177
+ migrate:down:
178
+ cmds:
179
+ - goose -dir internal/store/migrations postgres "$DATABASE_URL" down
180
+
181
+ ci:
182
+ desc: Everything CI does, locally
183
+ deps: [fmt, lint, test, build]
184
+ ```
185
+
186
+ `task` (no args) runs format + lint + test in parallel where possible. `task ci` runs the full pipeline.
187
+
188
+ ## `go.mod` template
189
+
190
+ ```go
191
+ module github.com/your-org/myservice
192
+
193
+ go 1.23
194
+
195
+ require (
196
+ github.com/caarlos0/env/v11 v11.2.2
197
+ github.com/gin-gonic/gin v1.10.1
198
+ github.com/go-playground/validator/v10 v10.22.1
199
+ github.com/google/uuid v1.6.0
200
+ github.com/jackc/pgx/v5 v5.7.6
201
+ golang.org/x/sync v0.18.0
202
+ )
203
+ ```
204
+
205
+ Only direct deps listed; `go mod tidy` populates indirects.
206
+
207
+ ## `.editorconfig`
208
+
209
+ ```ini
210
+ root = true
211
+
212
+ [*]
213
+ indent_style = tab
214
+ indent_size = 4
215
+ end_of_line = lf
216
+ charset = utf-8
217
+ trim_trailing_whitespace = true
218
+ insert_final_newline = true
219
+
220
+ [*.{yml,yaml,json,md}]
221
+ indent_style = space
222
+ indent_size = 2
223
+ ```
224
+
225
+ ## `.gitignore`
226
+
227
+ ```gitignore
228
+ bin/
229
+ coverage.out
230
+ coverage.html
231
+ *.test
232
+ *.prof
233
+
234
+ # IDE
235
+ .idea/
236
+ .vscode/
237
+ *.swp
238
+
239
+ # Local env
240
+ .env
241
+ .env.local
242
+
243
+ # Secrets
244
+ *.pem
245
+ *.key
246
+ ```
247
+
248
+ ## CI — minimal GitHub Actions
249
+
250
+ `.github/workflows/ci.yml`:
251
+
252
+ ```yaml
253
+ name: ci
254
+ on:
255
+ pull_request:
256
+ push:
257
+ branches: [main]
258
+
259
+ jobs:
260
+ ci:
261
+ runs-on: ubuntu-latest
262
+ steps:
263
+ - uses: actions/checkout@v4
264
+ - uses: actions/setup-go@v5
265
+ with:
266
+ go-version: '1.23'
267
+ cache: true
268
+
269
+ - name: Install tools
270
+ run: |
271
+ go install mvdan.cc/gofumpt@latest
272
+ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.0.0
273
+ go install go.uber.org/nilaway/cmd/nilaway@latest
274
+ go install github.com/go-task/task/v3/cmd/task@latest
275
+
276
+ - name: Format check
277
+ run: gofumpt -l . | (! grep .)
278
+
279
+ - name: Lint
280
+ run: golangci-lint run --timeout 5m ./...
281
+
282
+ - name: Nilaway
283
+ run: nilaway ./...
284
+
285
+ - name: Test
286
+ run: go test -race -shuffle=on -count=1 ./...
287
+
288
+ - name: Build
289
+ run: go build -trimpath ./...
290
+ ```
291
+
292
+ The order matters: format → lint → nilaway → test → build. Fail fast on the cheap checks.
293
+
294
+ ## `AGENTS.md` — agent-readable project facts
295
+
296
+ Every new project gets an `AGENTS.md` at the root. The content is **machine-friendly**: short, declarative, no marketing prose. Example:
297
+
298
+ ```markdown
299
+ # AGENTS.md
300
+
301
+ Go 1.23+ HTTP service for {one-line purpose}.
302
+
303
+ ## Commands
304
+ - `task` — fmt + lint + test
305
+ - `task build` — produce ./bin/server
306
+ - `task gen` — regenerate sqlc + mocks + proto
307
+
308
+ ## Architecture
309
+ - `cmd/server/main.go` — entrypoint, ≤50 LOC
310
+ - `internal/api/` — gin handlers + middleware
311
+ - `internal/domain/` — smart-constructor types, no I/O
312
+ - `internal/store/sqlc/` — generated; never hand-edit
313
+
314
+ ## Conventions
315
+ - `slog` for all logs; never `log.*`, never `fmt.Println`
316
+ - `context.Context` first arg for every public function
317
+ - Errors wrapped with `%w`; check with `errors.Is/As`
318
+ - 250 pure LOC ceiling per file — split before adding lines
319
+ ```
320
+
321
+ The skill's `cmd/new-project.go` writes this file with project-specific values filled in.
322
+
323
+ ## Sources
324
+
325
+ - Go modules reference: https://go.dev/ref/mod
326
+ - go-task: https://taskfile.dev
327
+ - golangci-lint v2: https://golangci-lint.run/docs/configuration/
328
+ - Standard project layout debate: https://go.dev/doc/modules/layout (NOT `golang-standards/project-layout` — that repo is community, not official)
@@ -0,0 +1,360 @@
1
+ # Bubbletea v2 — TUI with First-Class CJK / IME Support
2
+
3
+ The TUI stack for 2026. Use **v2 RC**, not v1. If your users include Korean, Japanese, or Chinese speakers, v1 is broken — IME composition lands in the wrong cells. v2 fixes this. This document is the canonical setup.
4
+
5
+ The reference implementation this document is distilled from: [`bubbletea-wm`](https://github.com/charmbracelet/bubbletea) — a floating window manager built specifically to nail down v2 + IME.
6
+
7
+ ---
8
+
9
+ ## Why v2 (not v1) — the IME story
10
+
11
+ Bubbletea v1 manages cursor positioning in software ("virtual cursor"). It draws a `█` at the cursor position. The terminal's *real* cursor stays at `(0, 0)`.
12
+
13
+ This breaks every CJK input method. IME candidate windows (the popup showing 가/각/간 for Korean Hangul composition, kana → kanji for Japanese, pinyin lookup for Chinese) anchor to the terminal's **real** cursor position. With v1, the candidate window appears at top-left while you are typing somewhere in the middle of the screen.
14
+
15
+ Bubbletea v2 fixes this with two changes:
16
+
17
+ 1. **`tea.View{Cursor: *tea.Cursor}`** — your `View()` method returns a view that *includes* the desired cursor position. The framework moves the terminal's real cursor there.
18
+ 2. **`textarea.SetVirtualCursor(false)`** — textareas no longer draw their own `█`. They expose `.Cursor()` so you can read where they want the real cursor.
19
+
20
+ Together: IME popups appear where the user is typing. As they should.
21
+
22
+ ### Other v2 wins (incidental)
23
+
24
+ - `tea.MouseClickMsg` / `MouseMotionMsg` / `MouseReleaseMsg` instead of one coarse `MouseMsg`.
25
+ - Cleaner `View` struct with `AltScreen`, `MouseMode` fields instead of `tea.Cmd` setters.
26
+ - Pluggable rendering pipeline; better performance under high message volume.
27
+
28
+ ---
29
+
30
+ ## `go.mod`
31
+
32
+ ```go
33
+ module github.com/your-org/mytui
34
+
35
+ go 1.23
36
+
37
+ require (
38
+ charm.land/bubbletea/v2 v2.0.0-rc.2
39
+ charm.land/bubbles/v2 v2.0.0-rc.1
40
+ charm.land/lipgloss/v2 v2.0.0-beta.3
41
+ github.com/mattn/go-runewidth v0.0.19
42
+ )
43
+ ```
44
+
45
+ The packages live under `charm.land/` (NOT `github.com/charmbracelet/...`) for v2. This is the Charm team's deliberate import-path break to keep v2 separate from v1 until stable.
46
+
47
+ ---
48
+
49
+ ## Minimal app — the IME-correct skeleton
50
+
51
+ ```go
52
+ package main
53
+
54
+ import (
55
+ "fmt"
56
+ "log"
57
+
58
+ tea "charm.land/bubbletea/v2"
59
+ "charm.land/bubbles/v2/textarea"
60
+ )
61
+
62
+ type model struct {
63
+ width, height int
64
+ ta textarea.Model
65
+ }
66
+
67
+ func initial() model {
68
+ ta := textarea.New()
69
+ ta.Placeholder = "Type Korean / Japanese / Chinese here..."
70
+ ta.SetWidth(60)
71
+ ta.SetHeight(10)
72
+ ta.SetVirtualCursor(false) // ← THE LINE. Without this, IME breaks.
73
+ ta.Focus()
74
+ return model{ta: ta}
75
+ }
76
+
77
+ func (m model) Init() tea.Cmd { return textarea.Blink }
78
+
79
+ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
80
+ switch msg := msg.(type) {
81
+ case tea.WindowSizeMsg:
82
+ m.width, m.height = msg.Width, msg.Height
83
+ case tea.KeyPressMsg:
84
+ if msg.String() == "ctrl+c" {
85
+ return m, tea.Quit
86
+ }
87
+ }
88
+ var cmd tea.Cmd
89
+ m.ta, cmd = m.ta.Update(msg)
90
+ return m, cmd
91
+ }
92
+
93
+ func (m model) View() tea.View {
94
+ var view tea.View
95
+ view.AltScreen = true
96
+ view.SetContent(m.ta.View())
97
+
98
+ // ── THE OTHER LINE. Position the REAL cursor for IME. ──
99
+ if cursor := m.ta.Cursor(); cursor != nil {
100
+ view.Cursor = cursor
101
+ }
102
+ return view
103
+ }
104
+
105
+ func main() {
106
+ if _, err := tea.NewProgram(initial(), tea.WithAltScreen()).Run(); err != nil {
107
+ log.Fatal(err)
108
+ }
109
+ fmt.Println("bye")
110
+ }
111
+ ```
112
+
113
+ The two lines that matter:
114
+
115
+ 1. `ta.SetVirtualCursor(false)` — disables the virtual `█`.
116
+ 2. `view.Cursor = cursor` (where `cursor = m.ta.Cursor()`) — exports the real cursor position to the framework.
117
+
118
+ Without **both**, IME breaks.
119
+
120
+ ---
121
+
122
+ ## CJK width — go-runewidth, not `len()`
123
+
124
+ Korean, Japanese, Chinese characters render as **two terminal cells** (wide characters per Unicode East Asian Width). Naive `len(string)` returns byte count, not display width. `utf8.RuneCountInString` returns rune count, also not display width.
125
+
126
+ Use `github.com/mattn/go-runewidth`:
127
+
128
+ ```go
129
+ import "github.com/mattn/go-runewidth"
130
+
131
+ func displayWidth(s string) int {
132
+ return runewidth.StringWidth(s)
133
+ }
134
+
135
+ // Wide character occupies two cells; pad accordingly
136
+ for _, r := range s {
137
+ cell := string(r)
138
+ w := runewidth.RuneWidth(r)
139
+ canvas = append(canvas, cell)
140
+ if w == 2 {
141
+ canvas = append(canvas, "") // placeholder for second cell
142
+ }
143
+ }
144
+ ```
145
+
146
+ `lipgloss/v2` uses `go-runewidth` internally — `lipgloss.Width("안녕")` returns 4, not 2. **If you measure outside lipgloss, you must call runewidth directly.**
147
+
148
+ ---
149
+
150
+ ## Mouse — v2 has typed events
151
+
152
+ ```go
153
+ case tea.MouseClickMsg:
154
+ // msg.X, msg.Y, msg.Button
155
+ return m.handleClick(msg.X, msg.Y, msg.Button)
156
+
157
+ case tea.MouseMotionMsg:
158
+ return m.handleHover(msg.X, msg.Y)
159
+
160
+ case tea.MouseReleaseMsg:
161
+ return m.handleRelease(msg.X, msg.Y)
162
+ ```
163
+
164
+ Enable mouse via the `View`:
165
+
166
+ ```go
167
+ view.MouseMode = tea.MouseModeCellMotion // or MouseModeAll
168
+ ```
169
+
170
+ `CellMotion` reports clicks + motion-while-button-pressed (drag). `MouseModeAll` reports motion always — heavier, only when you need hover.
171
+
172
+ ---
173
+
174
+ ## Components from `bubbles/v2`
175
+
176
+ ```go
177
+ import (
178
+ "charm.land/bubbles/v2/textarea"
179
+ "charm.land/bubbles/v2/textinput"
180
+ "charm.land/bubbles/v2/spinner"
181
+ "charm.land/bubbles/v2/viewport"
182
+ "charm.land/bubbles/v2/list"
183
+ "charm.land/bubbles/v2/table"
184
+ "charm.land/bubbles/v2/help"
185
+ "charm.land/bubbles/v2/key"
186
+ )
187
+ ```
188
+
189
+ All v2 components support `SetVirtualCursor(false)` where they accept text input. Use it for every text input that users might type CJK into — and "might" should be assumed *yes*.
190
+
191
+ ---
192
+
193
+ ## Styling — `lipgloss/v2`
194
+
195
+ ```go
196
+ import "charm.land/lipgloss/v2"
197
+
198
+ titleStyle := lipgloss.NewStyle().
199
+ Bold(true).
200
+ Foreground(lipgloss.Color("230")).
201
+ Background(lipgloss.Color("62")).
202
+ Padding(0, 1).
203
+ Border(lipgloss.RoundedBorder()).
204
+ BorderForeground(lipgloss.Color("63"))
205
+
206
+ rendered := titleStyle.Render("안녕하세요")
207
+ ```
208
+
209
+ `lipgloss/v2` width and padding correctly account for CJK display width. v1 did too — this is not a v2-specific fix, just a reminder.
210
+
211
+ ---
212
+
213
+ ## Architecture pattern — Model–Update–View
214
+
215
+ ```
216
+ +--------------------------------------------+
217
+ | tea.Program runs the event loop |
218
+ | |
219
+ | loop: |
220
+ | msg <- queue |
221
+ | model, cmd = model.Update(msg) |
222
+ | view = model.View() |
223
+ | render(view) |
224
+ | if cmd != nil: go run(cmd) -> queue |
225
+ +--------------------------------------------+
226
+ ```
227
+
228
+ Rules:
229
+
230
+ - **Model is a value type, not a pointer.** Bubbletea calls `Update` with a value receiver and expects a new value returned. Pointer receivers cause subtle bugs where state mutation leaks across draws.
231
+ - **`Update` is pure.** No I/O. No goroutines started inline. Any I/O returns a `tea.Cmd` — Bubbletea runs it in a goroutine and feeds the result back as a message.
232
+ - **`View` is read-only.** It returns a `tea.View` without modifying state.
233
+ - **`tea.Cmd` is `func() tea.Msg`.** It runs once, returns a message, exits. For repeating work, use `tea.Tick` or a self-resending command.
234
+
235
+ ```go
236
+ // One-shot command
237
+ func loadData() tea.Cmd {
238
+ return func() tea.Msg {
239
+ data, err := fetch()
240
+ if err != nil { return errMsg{err} }
241
+ return dataLoadedMsg{data}
242
+ }
243
+ }
244
+
245
+ // Periodic
246
+ func tickEvery() tea.Cmd {
247
+ return tea.Tick(time.Second, func(t time.Time) tea.Msg {
248
+ return tickMsg{t}
249
+ })
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Splitting the model — sub-models
256
+
257
+ ```go
258
+ type model struct {
259
+ list list.Model
260
+ input textinput.Model
261
+ spinner spinner.Model
262
+ }
263
+
264
+ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
265
+ var cmds []tea.Cmd
266
+ var cmd tea.Cmd
267
+
268
+ m.list, cmd = m.list.Update(msg)
269
+ cmds = append(cmds, cmd)
270
+
271
+ m.input, cmd = m.input.Update(msg)
272
+ cmds = append(cmds, cmd)
273
+
274
+ m.spinner, cmd = m.spinner.Update(msg)
275
+ cmds = append(cmds, cmd)
276
+
277
+ return m, tea.Batch(cmds...)
278
+ }
279
+ ```
280
+
281
+ `tea.Batch` runs commands concurrently. The framework collects their results in the order they arrive.
282
+
283
+ When the model exceeds 250 LOC, split by sub-model into separate files:
284
+
285
+ ```
286
+ internal/ui/
287
+ ├── model.go # root model orchestration
288
+ ├── list.go # list sub-model state + update + view
289
+ ├── input.go # input sub-model
290
+ └── spinner.go # spinner sub-model
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Testing TUI code — `teatest`
296
+
297
+ ```go
298
+ import "charm.land/bubbletea/v2/teatest"
299
+
300
+ func TestModel_typing_korean_keeps_cursor_in_position(t *testing.T) {
301
+ // Given
302
+ m := initial()
303
+ tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))
304
+
305
+ // When — simulate typing "안녕"
306
+ tm.Send(tea.KeyPressMsg{Code: '안'})
307
+ tm.Send(tea.KeyPressMsg{Code: '녕'})
308
+
309
+ // Then
310
+ out := tm.FinalOutput(t)
311
+ require.Contains(t, string(out), "안녕")
312
+ // Cursor should be at column 4 (two wide chars = 4 cells)
313
+ // ...
314
+ }
315
+ ```
316
+
317
+ `teatest` lets you drive the model through synthetic messages and inspect the rendered output. Pair with `autogold` snapshots for full-view regression tests.
318
+
319
+ ---
320
+
321
+ ## Common antipatterns
322
+
323
+ | Bad | Why | Good |
324
+ |---|---|---|
325
+ | `tea.Program` with `tea.WithoutSignals()` | Ctrl-C does not work | Default signal handling |
326
+ | Pointer receivers on Model | Bubbletea expects value semantics | Value receivers, return new model |
327
+ | `time.Sleep` inside `Update` | Blocks the event loop | `tea.Tick` or async `tea.Cmd` |
328
+ | `fmt.Println` for debug | Corrupts the rendered output | `tea.Printf` for logging, or write to a file |
329
+ | `len(s)` for CJK width | Off by 2x | `runewidth.StringWidth(s)` |
330
+ | `Bubbletea v1` for an app with text input | Korean/Japanese IME breaks | v2 + `SetVirtualCursor(false)` |
331
+ | Drawing your own `█` block cursor in v2 | Conflicts with `view.Cursor` | Let the terminal handle it |
332
+
333
+ ---
334
+
335
+ ## Performance — when v2 starts to crawl
336
+
337
+ - **Reduce View frequency.** If the model changes 60 times/sec but the rendered view changes once/sec, gate redraws on a "dirty" flag.
338
+ - **`viewport.Model` for scrollable content.** Avoid re-rendering thousands of lines on every keystroke.
339
+ - **`Batch` your commands.** A series of synchronous `tea.Cmd` returns serializes; `tea.Batch` parallelizes.
340
+ - **Profile with `tea.WithFPS(N)`** to cap repaint rate during development.
341
+
342
+ ---
343
+
344
+ ## When NOT to use Bubbletea
345
+
346
+ - The app is one prompt + one answer. Use `huh` (also from Charm) — simpler, no Model–Update–View ceremony.
347
+ - The app is a long-running daemon with occasional status output. Use `slog` to stderr and `tea.Program` only if interactivity becomes necessary.
348
+ - The app must run as a non-tty subprocess (CI, redirected stdin). `tea.Program` requires a tty for input. Detect via `term.IsTerminal(int(os.Stdin.Fd()))` and fall back to a non-interactive path.
349
+
350
+ ---
351
+
352
+ ## Sources
353
+
354
+ - bubbletea v2 RC: https://github.com/charmbracelet/bubbletea/tree/v2
355
+ - bubbles v2: https://github.com/charmbracelet/bubbles/tree/v2
356
+ - lipgloss v2: https://github.com/charmbracelet/lipgloss/tree/v2
357
+ - bubbletea-wm (IME reference): see charmbracelet/bubbletea upstream
358
+ - crush CLI (production IME impl): https://github.com/charmbracelet/crush
359
+ - go-runewidth: https://github.com/mattn/go-runewidth
360
+ - Unicode East Asian Width: https://www.unicode.org/reports/tr11/