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,201 @@
1
+ # Textual TUI
2
+
3
+ Textual builds rich, mouse-aware, scrollable, mobile-style TUIs on top of `rich`. Replaces curses, urwid, blessed.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv add textual
9
+ uv add --dev textual-dev # textual console + run --dev for hot reload
10
+ ```
11
+
12
+ ## Minimal app
13
+
14
+ ```python
15
+ from textual.app import App, ComposeResult
16
+ from textual.widgets import Header, Footer, Button, Label
17
+ from textual.containers import Vertical
18
+
19
+
20
+ class CounterApp(App[None]):
21
+ """A trivial counter app."""
22
+
23
+ BINDINGS = [("q", "quit", "Quit")]
24
+ CSS = """
25
+ #count {
26
+ height: 3;
27
+ content-align: center middle;
28
+ background: $boost;
29
+ }
30
+ """
31
+
32
+ count: int = 0
33
+
34
+ def compose(self) -> ComposeResult:
35
+ yield Header()
36
+ with Vertical():
37
+ yield Label("0", id="count")
38
+ yield Button("Increment", id="inc", variant="primary")
39
+ yield Button("Reset", id="reset", variant="warning")
40
+ yield Footer()
41
+
42
+ def on_button_pressed(self, event: Button.Pressed) -> None:
43
+ if event.button.id == "inc":
44
+ self.count += 1
45
+ elif event.button.id == "reset":
46
+ self.count = 0
47
+ self.query_one("#count", Label).update(str(self.count))
48
+
49
+
50
+ if __name__ == "__main__":
51
+ CounterApp().run()
52
+ ```
53
+
54
+ Run:
55
+
56
+ ```bash
57
+ uv run python counter.py
58
+ ```
59
+
60
+ For hot reload during development:
61
+
62
+ ```bash
63
+ uv run textual run --dev counter.py
64
+ ```
65
+
66
+ ## Reactive attributes
67
+
68
+ Textual's `reactive()` descriptor turns a class attribute into something that watches assignments and re-renders automatically. Replaces the manual `query_one` + `update` dance.
69
+
70
+ ```python
71
+ from textual.app import App, ComposeResult
72
+ from textual.reactive import reactive
73
+ from textual.widgets import Label
74
+
75
+
76
+ class CountWidget(Label):
77
+ count: reactive[int] = reactive(0)
78
+
79
+ def render(self) -> str:
80
+ return f"Count: {self.count}"
81
+
82
+
83
+ class CounterApp(App[None]):
84
+ def compose(self) -> ComposeResult:
85
+ yield CountWidget()
86
+
87
+ def on_key(self, event) -> None:
88
+ if event.key == "space":
89
+ self.query_one(CountWidget).count += 1
90
+ ```
91
+
92
+ `reactive()` triggers `render()` (or `watch_<attr>` and `validate_<attr>` callbacks if defined). Use `recompose=True` if you need to call `compose()` again on change.
93
+
94
+ ## Async work — workers
95
+
96
+ NEVER block the event loop. For network/disk/CPU work, use `@work` (creates a worker) or `run_worker`.
97
+
98
+ ```python
99
+ import httpx
100
+ from textual.app import App, ComposeResult
101
+ from textual.widgets import Input, Static
102
+ from textual.work import work
103
+
104
+
105
+ class FetchApp(App[None]):
106
+ def compose(self) -> ComposeResult:
107
+ yield Input(placeholder="URL", id="url")
108
+ yield Static(id="result")
109
+
110
+ @work(exclusive=True)
111
+ async def fetch(self, url: str) -> None:
112
+ async with httpx.AsyncClient(timeout=10.0) as client:
113
+ response = await client.get(url)
114
+ self.query_one("#result", Static).update(f"{response.status_code} - {len(response.text)} bytes")
115
+
116
+ def on_input_submitted(self, event: Input.Submitted) -> None:
117
+ self.fetch(event.value)
118
+ ```
119
+
120
+ `exclusive=True` cancels the previous worker if the user submits a new URL before the first finishes. Workers integrate with Textual's lifecycle - they're cancelled when the app exits.
121
+
122
+ `@work` is asyncio-flavoured under the hood. That is fine - it does not violate the no-asyncio rule because you are calling Textual's API, not importing asyncio yourself. Inside the worker body, use `httpx.AsyncClient` and other anyio-friendly libraries.
123
+
124
+ ## Action handlers
125
+
126
+ Bind keys to method calls via `BINDINGS` and `action_*` methods.
127
+
128
+ ```python
129
+ class App(App):
130
+ BINDINGS = [
131
+ ("ctrl+s", "save", "Save"),
132
+ ("ctrl+r", "reload", "Reload"),
133
+ ]
134
+
135
+ def action_save(self) -> None:
136
+ # Called on ctrl+s
137
+ ...
138
+
139
+ def action_reload(self) -> None:
140
+ ...
141
+ ```
142
+
143
+ Bindings can also include the `priority=True` flag to fire before children get a chance.
144
+
145
+ ## CSS
146
+
147
+ Textual's CSS supports selectors, variables (`$primary`, `$boost`), animations. Inline via `CSS = "..."` or external via `CSS_PATH = "app.tcss"`.
148
+
149
+ ```css
150
+ Screen {
151
+ background: $surface;
152
+ color: $text;
153
+ layout: vertical;
154
+ }
155
+
156
+ #sidebar {
157
+ width: 30;
158
+ background: $boost;
159
+ }
160
+
161
+ Button.danger {
162
+ background: $error;
163
+ }
164
+ ```
165
+
166
+ Reload with `r` in dev mode (`textual run --dev`).
167
+
168
+ ## Testing
169
+
170
+ ```python
171
+ import pytest
172
+ from myapp import CounterApp
173
+
174
+
175
+ @pytest.mark.anyio
176
+ async def test_counter_increments() -> None:
177
+ app = CounterApp()
178
+ async with app.run_test() as pilot:
179
+ await pilot.click("#inc")
180
+ await pilot.click("#inc")
181
+ assert app.count == 2
182
+ ```
183
+
184
+ `pilot.click(selector)`, `pilot.press("q")`, `pilot.pause()` for waiting on the next frame.
185
+
186
+ ## When NOT to use Textual
187
+
188
+ | Need | Use |
189
+ |---|---|
190
+ | One-off CLI with structured output | typer + rich |
191
+ | Progress bar in a script | rich.progress |
192
+ | Tabular display of query results | rich.table |
193
+ | Full-screen app with state, input, mouse | Textual |
194
+
195
+ A pretty CLI is not a TUI. Reach for Textual when the user expects to navigate a UI, not when you want colours.
196
+
197
+ ## Sources
198
+
199
+ - Textual docs: <https://textual.textualize.io>
200
+ - Textual tutorial: <https://textual.textualize.io/tutorial/>
201
+ - API reference: <https://textual.textualize.io/api/>
@@ -0,0 +1,176 @@
1
+ # Type Patterns
2
+
3
+ How to use Python's type system to catch bugs at check time, not runtime.
4
+
5
+ ---
6
+
7
+ ## NewType — distinct primitives
8
+
9
+ Same runtime type, different meaning. The type checker prevents mixing.
10
+
11
+ ```python
12
+ from typing import NewType
13
+
14
+ UserId = NewType("UserId", int)
15
+ MovieId = NewType("MovieId", int)
16
+ Email = NewType("Email", str)
17
+ Seconds = NewType("Seconds", float)
18
+ Milliseconds = NewType("Milliseconds", float)
19
+
20
+ def get_user(user_id: UserId) -> User: ...
21
+ def get_movie(movie_id: MovieId) -> Movie: ...
22
+ def sleep(duration: Seconds) -> None: ...
23
+
24
+ uid = UserId(42)
25
+ mid = MovieId(42)
26
+
27
+ get_user(uid) # OK
28
+ get_user(mid) # type error: MovieId is not UserId
29
+ get_user(42) # type error: int is not UserId
30
+ sleep(Milliseconds(100.0)) # type error
31
+ ```
32
+
33
+ **Use when**: IDs, indices, keys, units of measurement — any pair where swapping is a bug.
34
+ **Skip when**: ephemeral local math where branding adds noise with zero safety gain.
35
+
36
+ ---
37
+
38
+ ## Final — constants are const
39
+
40
+ Module-level constants declare their intent. Reassignment is a type error.
41
+
42
+ ```python
43
+ from typing import Final
44
+
45
+ MAX_RETRIES: Final = 3
46
+ API_BASE_URL: Final = "https://api.example.com"
47
+ DEFAULT_TIMEOUT: Final = 30.0
48
+
49
+ MAX_RETRIES = 5 # type error: cannot assign to Final
50
+ ```
51
+
52
+ If it changes at runtime, it's not a constant — make it a function parameter or config field.
53
+
54
+ ---
55
+
56
+ ## TypeAlias — name complex types
57
+
58
+ If a union or generic appears more than once, give it a name.
59
+
60
+ ```python
61
+ # Python 3.12+
62
+ type JsonValue = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]
63
+ type Headers = dict[str, str]
64
+ type Middleware = Callable[[Request], Awaitable[Response]]
65
+
66
+ # Pre-3.12
67
+ from typing import TypeAlias
68
+
69
+ JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]
70
+ ```
71
+
72
+ ---
73
+
74
+ ## StrEnum / IntEnum — closed sets
75
+
76
+ Any fixed set of known values. No string literals scattered through code.
77
+
78
+ ```python
79
+ from enum import StrEnum, IntEnum, unique
80
+
81
+ @unique
82
+ class Role(StrEnum):
83
+ ADMIN = "admin"
84
+ USER = "user"
85
+ GUEST = "guest"
86
+
87
+ @unique
88
+ class HttpStatus(IntEnum):
89
+ OK = 200
90
+ NOT_FOUND = 404
91
+ INTERNAL_ERROR = 500
92
+
93
+ # BAD
94
+ def check_role(role: str) -> bool: ...
95
+
96
+ # GOOD
97
+ def check_role(role: Role) -> bool: ...
98
+ ```
99
+
100
+ `StrEnum` when values serialize as strings (API, DB). `IntEnum` for numeric codes. Plain `Enum` for pure labels.
101
+
102
+ ---
103
+
104
+ ## Type narrowing — let the checker follow your logic
105
+
106
+ `isinstance`, `is None`, and `match` narrow types automatically. Use them instead of `cast`.
107
+
108
+ ```python
109
+ def process(value: str | int | None) -> str:
110
+ if value is None:
111
+ return "nothing"
112
+ # checker knows: str | int
113
+
114
+ if isinstance(value, str):
115
+ return value.upper()
116
+ # checker knows: int
117
+
118
+ return str(value * 2)
119
+ ```
120
+
121
+ ### TypeGuard for custom narrowing
122
+
123
+ ```python
124
+ from typing import TypeGuard
125
+
126
+ def is_valid_email(value: str) -> TypeGuard[Email]:
127
+ return "@" in value and "." in value.split("@")[1]
128
+
129
+ def send(addr: str) -> None:
130
+ if not is_valid_email(addr):
131
+ raise ValueError(addr)
132
+ # checker knows: addr is Email
133
+ deliver(addr)
134
+ ```
135
+
136
+ ### TypeIs (Python 3.13+) — the strict version
137
+
138
+ `TypeIs` is stricter than `TypeGuard` — it narrows in both `if` and `else` branches.
139
+
140
+ ```python
141
+ from typing import TypeIs
142
+
143
+ def is_str(value: str | int) -> TypeIs[str]:
144
+ return isinstance(value, str)
145
+
146
+ def handle(v: str | int) -> None:
147
+ if is_str(v):
148
+ print(v.upper()) # checker knows: str
149
+ else:
150
+ print(v + 1) # checker knows: int
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Union syntax
156
+
157
+ Always `X | Y`. Never `Union[X, Y]` or `Optional[X]`.
158
+
159
+ ```python
160
+ # BAD
161
+ from typing import Union, Optional
162
+ def f(x: Optional[int]) -> Union[str, int]: ...
163
+
164
+ # GOOD
165
+ def f(x: int | None) -> str | int: ...
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Sources
171
+
172
+ - Python docs: [typing — NewType](https://docs.python.org/3/library/typing.html#newtype)
173
+ - Python docs: [typing — Final](https://docs.python.org/3/library/typing.html#typing.Final)
174
+ - Python docs: [typing — TypeGuard](https://docs.python.org/3/library/typing.html#typing.TypeGuard)
175
+ - PEP 604: [Union syntax X | Y](https://peps.python.org/pep-0604/)
176
+ - PEP 742: [TypeIs](https://peps.python.org/pep-0742/)
@@ -0,0 +1,317 @@
1
+ # Rust Programmer
2
+
3
+ Production Rust in 2026. **Explicit allocation, compile-time proof, zero hidden cost.** Type-state-first, unsafe-banished-by-default, agent-proof.
4
+
5
+ ## Identity — What Kind of Rust You Write
6
+
7
+ You write Rust that looks like a Zig programmer designed it and a Rust compiler enforces it. Every allocation is visible. Every cost is explicit. Every invariant is encoded in the type system. Every cleanup is deterministic. The borrow checker, lifetime analysis, trait bounds, and `miri` then guarantee what Zig leaves to discipline.
8
+
9
+ **Five pillars, every file, no exceptions:**
10
+
11
+ | Pillar | Default Behavior | Reference |
12
+ |---|---|---|
13
+ | **Explicit allocation** | Arena for hot paths, `&[T]`/`Cow` over `Vec`/`String` in signatures, `try_*` when allocation can fail | [zero-cost-safety.md §1](zero-cost-safety.md) |
14
+ | **Compile-time proof** | `const fn` everything const-eligible, `const { assert!(...) }` for compile-time guards, const generics for sized buffers | [zero-cost-safety.md §2](zero-cost-safety.md) |
15
+ | **Zero hidden cost** | Slice-based APIs where caller owns memory, no hidden `.clone()`/`.to_string()`, `Cow` to defer allocation | [zero-cost-safety.md §3](zero-cost-safety.md) |
16
+ | **Type-encoded invariants** | Newtype wrappers for every semantic unit, type-state for state machines, branded IDs | [type-state.md](type-state.md) |
17
+ | **Deterministic cleanup** | `scopeguard::guard` for errdefer, `Drop` for RAII, defuse-on-success for rollback | [zero-cost-safety.md §5](zero-cost-safety.md) |
18
+
19
+ The two highest-leverage tools Rust gives a coding agent:
20
+
21
+ 1. **Bounded polymorphism** (traits). Real, machine-checked, composable constraints.
22
+ 2. **Newtype-as-coordinate-space.** `Point<Screen>` and `Point<World>` are distinct types — the agent literally cannot pass one where the other is expected. This is the `euclid` crate pattern; generalize ruthlessly to money, durations, IDs, byte offsets, char offsets, paths rooted at different bases. Full patterns → [type-state.md](type-state.md).
23
+
24
+ ---
25
+
26
+ ## Hard Rules (Every `.rs` File)
27
+
28
+ ### 1. No `unwrap()`, No `expect()` Outside Tests
29
+
30
+ ```rust
31
+ // WRONG
32
+ let val = map.get("key").unwrap();
33
+
34
+ // RIGHT — propagate or provide context
35
+ let val = map.get("key").context("missing 'key' in config")?;
36
+ ```
37
+
38
+ Typed errors for libraries ([thiserror](https://docs.rs/thiserror)), ad-hoc errors for binaries ([anyhow](https://docs.rs/anyhow) / [color-eyre](https://docs.rs/color-eyre)). Full stack → [libraries.md](libraries.md).
39
+
40
+ ### 2. No `unsafe` Without Miri Proof
41
+
42
+ If `unsafe` is unavoidable, you have miri. Run it. Always. **Load [`../rust-ub/README.md`](../rust-ub/README.md) plus every file under [`../rust-ub/`](../rust-ub/)** for the full UB taxonomy, Miri escalation protocol (4 strictness levels), and the fix-and-prove workflow. Every `unsafe` block needs the three components from [unsafe-discipline.md](unsafe-discipline.md): safe wrapper, `// SAFETY:` comment, miri test.
43
+
44
+ ```bash
45
+ cargo +nightly miri nextest run
46
+ ```
47
+
48
+ ### 3. Explicit Allocation — Arena by Default in Hot Paths
49
+
50
+ **Do not scatter `Box::new()` / `Vec::new()` across hot loops.** Use arena allocation to make allocation scope visible and bulk-freeable. Full recipes → [zero-cost-safety.md §1](zero-cost-safety.md).
51
+
52
+ ```rust
53
+ use bumpalo::Bump;
54
+
55
+ fn parse_frame<'a>(arena: &'a Bump, raw: &[u8]) -> Frame<'a> {
56
+ let header = arena.alloc(parse_header(raw));
57
+ let payload = arena.alloc_slice_copy(&raw[HEADER_LEN..]);
58
+ Frame { header, payload }
59
+ }
60
+ // Caller owns arena. Caller decides when memory dies. Zero individual frees.
61
+ ```
62
+
63
+ When arena is overkill (simple CLI, one-shot allocation), `Vec`/`String` are fine — but **function signatures still prefer borrows**:
64
+
65
+ ```rust
66
+ // WRONG — forces caller to allocate
67
+ fn process(input: String) -> String { ... }
68
+
69
+ // RIGHT — caller chooses allocation strategy
70
+ fn process(input: &str) -> Cow<'_, str> { ... }
71
+
72
+ // BEST for hot paths — zero allocation, caller provides buffer
73
+ fn process(input: &[u8], output: &mut [u8]) -> usize { ... }
74
+ ```
75
+
76
+ ### 4. Compile-Time First — const fn Everything Const-Eligible
77
+
78
+ If a function CAN be `const fn`, it MUST be `const fn`. Full recipes → [zero-cost-safety.md §2](zero-cost-safety.md).
79
+
80
+ ```rust
81
+ // Lookup tables computed at compile time — zero runtime cost
82
+ const CRC_TABLE: [u32; 256] = {
83
+ let mut table = [0u32; 256];
84
+ let mut i = 0;
85
+ while i < 256 {
86
+ let mut crc = i as u32;
87
+ let mut j = 0;
88
+ while j < 8 {
89
+ crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB88320 } else { crc >> 1 };
90
+ j += 1;
91
+ }
92
+ table[i] = crc;
93
+ i += 1;
94
+ }
95
+ table
96
+ };
97
+
98
+ // Compile-time assertions — catch violations at build time, not runtime
99
+ const { assert!(std::mem::size_of::<Header>() == 12, "Header must be 12 bytes") };
100
+ ```
101
+
102
+ Use `const generics` for stack-allocated buffers with compile-time size:
103
+
104
+ ```rust
105
+ struct RingBuffer<T, const N: usize> {
106
+ data: [MaybeUninit<T>; N],
107
+ head: usize,
108
+ len: usize,
109
+ }
110
+ ```
111
+
112
+ ### 5. Scope Guards — Deterministic Cleanup on Every Path
113
+
114
+ Zig's `errdefer` in Rust. Full recipes → [zero-cost-safety.md §5](zero-cost-safety.md).
115
+
116
+ ```rust
117
+ use scopeguard::guard;
118
+
119
+ fn deploy(artifact: &Path) -> Result<(), DeployError> {
120
+ let backup = snapshot_current()?;
121
+ // errdefer: restore on failure
122
+ let rollback = guard(backup, |b| { let _ = restore(&b); });
123
+
124
+ upload(artifact)?;
125
+ health_check()?;
126
+
127
+ // Success: defuse the guard
128
+ scopeguard::ScopeGuard::into_inner(rollback);
129
+ Ok(())
130
+ }
131
+ ```
132
+
133
+ ### 6. Bit-Level Layout — zerocopy for Wire Formats
134
+
135
+ Never hand-write `transmute` or pointer casts for parsing binary data. Full recipes → [zero-cost-safety.md §4](zero-cost-safety.md).
136
+
137
+ ```rust
138
+ use zerocopy::{FromBytes, IntoBytes, KnownLayout, Immutable};
139
+
140
+ #[derive(FromBytes, IntoBytes, KnownLayout, Immutable)]
141
+ #[repr(C)]
142
+ struct PacketHeader {
143
+ magic: [u8; 4],
144
+ version: u8,
145
+ flags: u8,
146
+ length: [u8; 2], // use byte array for packed fields, decode via from_le_bytes
147
+ }
148
+ ```
149
+
150
+ ### 7. Exhaustive Match — No Wildcard on Enums You Control
151
+
152
+ ```rust
153
+ // WRONG — silently ignores new variants
154
+ match status {
155
+ Status::Ok => handle_ok(),
156
+ _ => handle_error(),
157
+ }
158
+
159
+ // RIGHT — compiler forces update when variants change
160
+ match status {
161
+ Status::Ok => handle_ok(),
162
+ Status::NotFound => handle_not_found(),
163
+ Status::Timeout => handle_timeout(),
164
+ }
165
+ ```
166
+
167
+ For `#[non_exhaustive]` enums from external crates, the wildcard `_` is required — but add a `tracing::warn!` in the catch-all so you notice when new variants appear.
168
+
169
+ ### 8. Type-State Over Runtime Checks
170
+
171
+ Never `if self.state == State::Validated`. Encode states as distinct types so the compiler refuses invalid transitions. Full patterns → [type-state.md](type-state.md).
172
+
173
+ ```rust
174
+ struct Order<S: OrderState> { data: OrderData, _state: PhantomData<S> }
175
+ struct Draft;
176
+ struct Validated;
177
+ struct Paid;
178
+
179
+ impl Order<Draft> {
180
+ fn validate(self) -> Result<Order<Validated>, ValidationError> { ... }
181
+ }
182
+ impl Order<Validated> {
183
+ fn pay(self, payment: Payment) -> Result<Order<Paid>, PaymentError> { ... }
184
+ }
185
+ // Order<Draft> has no .pay() method. Compiler enforces the workflow.
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Standard Library Defaults
191
+
192
+ Full decision tree with rationale and code snippets → [libraries.md](libraries.md).
193
+
194
+ | Category | Crate | Why |
195
+ |---|---|---|
196
+ | Async runtime | `tokio` | Ecosystem standard. Patterns → [async-tokio.md](async-tokio.md) |
197
+ | HTTP server | `axum` + `tower` | Type-safe extractors, tower middleware. Stack → [axum-stack.md](axum-stack.md) |
198
+ | CLI | `clap` derive + `color-eyre` | Typed args, beautiful errors. Stack → [clap-stack.md](clap-stack.md) |
199
+ | Serialization | `serde` + `serde_json` | Non-negotiable for any boundary type |
200
+ | Error (library) | `thiserror` | Derive `Error` with zero boilerplate |
201
+ | Error (binary) | `anyhow` / `color-eyre` | Context-rich ad-hoc errors |
202
+ | Database | `sqlx` (compile-time checked) | No runtime SQL surprises |
203
+ | Arena alloc | `bumpalo` / `typed-arena` | Explicit allocation scope. Patterns → [zero-cost-safety.md §1](zero-cost-safety.md) |
204
+ | Zero-copy parse | `zerocopy` | Safe binary parsing, no transmute. Patterns → [zero-cost-safety.md §4](zero-cost-safety.md) |
205
+ | Scope guard | `scopeguard` | errdefer/defer. Patterns → [zero-cost-safety.md §5](zero-cost-safety.md) |
206
+ | Stack collections | `smallvec` / `arrayvec` / `tinyvec` | Stack-first, heap-spillover. Patterns → [zero-cost-safety.md §3](zero-cost-safety.md) |
207
+ | Bitfield | `bitfield` / `modular-bitfield` | Bit-packed flags. Patterns → [zero-cost-safety.md §4](zero-cost-safety.md) |
208
+ | Testing | `proptest` + `insta` | Property + snapshot tests. Patterns → [proptest-insta.md](proptest-insta.md) |
209
+ | Concurrency | `tokio::sync` / `parking_lot` | Channel-first, lock-second. Patterns → [concurrency.md](concurrency.md) |
210
+
211
+ ---
212
+
213
+ ## Cargo Strict Configuration
214
+
215
+ Every new project gets the strict lint config from [cargo-strict.md](cargo-strict.md). The non-negotiable CI gate:
216
+
217
+ ```bash
218
+ cargo fmt --all -- --check && \
219
+ cargo clippy --all-targets --all-features -- -D warnings && \
220
+ cargo nextest run && \
221
+ cargo +nightly miri nextest run # when unsafe is involved
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Code Review Checklist (Post-Write, Every PR)
227
+
228
+ Run through this list after writing any Rust code. Every item links to its recipe.
229
+
230
+ | # | Check | Fix Reference |
231
+ |---|---|---|
232
+ | 1 | Every function signature prefers `&[T]`/`&str`/`Cow` over owned types | [zero-cost-safety.md §3](zero-cost-safety.md) |
233
+ | 2 | Hot-path allocations use arena (`bumpalo`) not scattered `Box`/`Vec` | [zero-cost-safety.md §1](zero-cost-safety.md) |
234
+ | 3 | Const-eligible functions are `const fn` | [zero-cost-safety.md §2](zero-cost-safety.md) |
235
+ | 4 | Lookup tables / config constants computed at compile time | [zero-cost-safety.md §2](zero-cost-safety.md) |
236
+ | 5 | Binary format parsing uses `zerocopy`, not `transmute` | [zero-cost-safety.md §4](zero-cost-safety.md) |
237
+ | 6 | Cleanup logic uses `scopeguard` or `Drop`, never manual `if err` cleanup | [zero-cost-safety.md §5](zero-cost-safety.md) |
238
+ | 7 | Distinct semantic units are newtypes, not primitive aliases | [type-state.md](type-state.md) |
239
+ | 8 | State machines use type-state, not runtime `if state ==` | [type-state.md](type-state.md) |
240
+ | 9 | No `unwrap()`/`expect()` outside `#[cfg(test)]` | [libraries.md](libraries.md) |
241
+ | 10 | Every `unsafe` has SAFETY comment + miri test | [unsafe-discipline.md](unsafe-discipline.md), [../rust-ub/](../rust-ub/) |
242
+ | 11 | Match on owned enums is exhaustive (no `_ =>`) | This file §7 |
243
+ | 12 | Clippy pedantic passes with zero warnings | [cargo-strict.md](cargo-strict.md) |
244
+ | 13 | Property tests exist for any function with a nontrivial domain | [proptest-insta.md](proptest-insta.md) |
245
+ | 14 | Concurrency uses channels first, locks second, atomics last | [concurrency.md](concurrency.md) |
246
+ | 15 | Async code uses `JoinSet` for structured concurrency | [async-tokio.md](async-tokio.md) |
247
+
248
+ ---
249
+
250
+ ## Default Cargo.toml Dependencies — Zero-Cost Safety Stack
251
+
252
+ Every new project starts with these alongside the standard deps from [cargo-strict.md](cargo-strict.md):
253
+
254
+ ```toml
255
+ # Zero-cost safety stack
256
+ bumpalo = { version = "3", features = ["collections"] }
257
+ scopeguard = "1"
258
+ smallvec = { version = "1", features = ["union", "const_generics"] }
259
+ zerocopy = { version = "0.8", features = ["derive"] }
260
+
261
+ # Add when needed:
262
+ # typed-arena = "2" # homogeneous arena
263
+ # arrayvec = "0.7" # fixed-capacity stack vec
264
+ # tinyvec = { version = "1", features = ["alloc"] }
265
+ # bitfield = "0.17" # bit-packed flags
266
+ # modular-bitfield = "0.11" # richer bitfield API
267
+ # bytemuck = { version = "1", features = ["derive"] }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Reference Index
273
+
274
+ | File | When to Load |
275
+ |---|---|
276
+ | [zero-cost-safety.md](zero-cost-safety.md) | Arena, allocator, const fn, comptime, zero-alloc, bitfield, repr, scopeguard, errdefer, Zig-like patterns |
277
+ | [type-state.md](type-state.md) | Newtype wrappers, type-state machines, branded IDs, phantom types |
278
+ | [unsafe-discipline.md](unsafe-discipline.md) | Any `unsafe` block — SAFETY comments, safe wrappers, miri proof |
279
+ | [libraries.md](libraries.md) | Library selection, crate decision tree, dependency audit |
280
+ | [cargo-strict.md](cargo-strict.md) | Project bootstrap, lint config, CI gate commands |
281
+ | [async-tokio.md](async-tokio.md) | Async runtime, spawning, cancellation, `JoinSet`, `select!` |
282
+ | [axum-stack.md](axum-stack.md) | HTTP services — axum + sqlx + tower + tracing |
283
+ | [clap-stack.md](clap-stack.md) | CLI tools — clap derive + color-eyre + indicatif |
284
+ | [concurrency.md](concurrency.md) | Locks, atomics, channels, loom model checker |
285
+ | [proptest-insta.md](proptest-insta.md) | Property tests, snapshot tests, round-trip invariants |
286
+ | [one-liners.md](one-liners.md) | `rust-script` one-liners, disposable scripts, inline deps |
287
+ | [../rust-ub/README.md](../rust-ub/README.md) | UB hunting — miri escalation, sanitizers, fuzzing |
288
+ | [../rust-ub/ub-taxonomy.md](../rust-ub/ub-taxonomy.md) | 14-category UB taxonomy with detection status |
289
+ | [../rust-ub/miri-sanitizers-loom.md](../rust-ub/miri-sanitizers-loom.md) | Miri flags, ASAN/TSAN/MSAN, loom, cargo-fuzz |
290
+
291
+ ---
292
+
293
+ ## The Shape of Every Function
294
+
295
+ ```rust
296
+ /// One-line doc explaining WHAT, not HOW.
297
+ ///
298
+ /// # Errors
299
+ /// Returns `FooError::Bar` when the input is invalid.
300
+ const fn frobnicate<'a>(
301
+ arena: &'a Bump, // explicit allocator when arena is in play
302
+ input: &[u8], // borrow, not owned
303
+ output: &mut [u8], // caller-provided buffer
304
+ ) -> Result<&'a Frob, FrobError> {
305
+ // ...
306
+ }
307
+ ```
308
+
309
+ **Why this shape:** the caller sees every cost. Allocation scope is the arena's lifetime. Input is borrowed. Output buffer is caller-owned. Error is typed. The compiler enforces all of it.
310
+
311
+ ---
312
+
313
+ ## Activation
314
+
315
+ This skill activates whenever you are writing or modifying any `.rs` file or `Cargo.toml`. One-off scripts get the strict treatment too — `rust-script` + the same lints, the same gates. Details → [one-liners.md](one-liners.md).
316
+
317
+ **The promise:** production hygiene with throwaway ergonomics. Explicit allocation, compile-time proof, zero hidden cost, and **agent-proof safety at any volume**.