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,152 @@
1
+ # Strict tsconfig + Biome
2
+
3
+ The canonical ultra-strict config. Copy-paste, then add your own paths.
4
+
5
+ ---
6
+
7
+ ## tsconfig.json
8
+
9
+ ```jsonc
10
+ {
11
+ "compilerOptions": {
12
+ // ── Strict core ──────────────────────────────────────────
13
+ "strict": true, // enables all strict* flags below
14
+ // strict includes: strictNullChecks, strictFunctionTypes,
15
+ // strictBindCallApply, strictPropertyInitialization,
16
+ // noImplicitAny, noImplicitThis, alwaysStrict, useUnknownInCatchVariables
17
+
18
+ // ── Additional strict flags (NOT included in "strict") ──
19
+ "noUncheckedIndexedAccess": true, // obj[key] is T | undefined, not T
20
+ "exactOptionalPropertyTypes": true, // { x?: string } !== { x: string | undefined }
21
+ "noFallthroughCasesInSwitch": true, // switch fall-through is an error
22
+ "noPropertyAccessFromIndexSignature": true, // forces bracket notation for index sigs
23
+ "forceConsistentCasingInFileNames": true, // prevents case-sensitivity bugs on macOS/Win
24
+
25
+ // ── Module system ────────────────────────────────────────
26
+ "module": "ESNext",
27
+ "moduleResolution": "bundler",
28
+ "verbatimModuleSyntax": true, // forces `import type` for type-only imports
29
+ "isolatedModules": true, // safe for esbuild / swc / Bun transpilation
30
+ "esModuleInterop": true,
31
+ "resolveJsonModule": true,
32
+
33
+ // ── Target ───────────────────────────────────────────────
34
+ "target": "ESNext",
35
+ "lib": ["ESNext"],
36
+
37
+ // ── Emit ─────────────────────────────────────────────────
38
+ "declaration": true,
39
+ "declarationMap": true,
40
+ "sourceMap": true,
41
+ "outDir": "dist",
42
+ "rootDir": "src",
43
+
44
+ // ── Performance ──────────────────────────────────────────
45
+ "skipLibCheck": true, // skip checking .d.ts files for speed
46
+ "incremental": true
47
+ },
48
+ "include": ["src"],
49
+ "exclude": ["node_modules", "dist"]
50
+ }
51
+ ```
52
+
53
+ ### What each extra flag catches
54
+
55
+ | Flag | What it prevents |
56
+ |---|---|
57
+ | `noUncheckedIndexedAccess` | `arr[0]` is `T \| undefined`, not `T`. Forces you to check before using. |
58
+ | `exactOptionalPropertyTypes` | `{ x?: string }` means "missing or string", NOT "string \| undefined". Assigns `undefined` explicitly? Type error. |
59
+ | `noFallthroughCasesInSwitch` | Forgetting `break` / `return` in a switch case. |
60
+ | `noPropertyAccessFromIndexSignature` | `obj.foo` on `Record<string, X>` is an error. Use `obj["foo"]`. |
61
+ | `verbatimModuleSyntax` | Forces `import type { X }` for type-only imports. Prevents runtime import of types. |
62
+
63
+ ### Bun-specific additions
64
+
65
+ For Bun projects, add to `compilerOptions`:
66
+ ```jsonc
67
+ {
68
+ "types": ["bun-types"],
69
+ "moduleDetection": "force"
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## biome.jsonc
76
+
77
+ ```jsonc
78
+ {
79
+ "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
80
+ "organizeImports": {
81
+ "enabled": true
82
+ },
83
+ "linter": {
84
+ "enabled": true,
85
+ "rules": {
86
+ "recommended": true,
87
+ "suspicious": {
88
+ "noExplicitAny": "error",
89
+ "noConfusingVoidType": "error",
90
+ "noFallthroughSwitchClause": "error"
91
+ },
92
+ "style": {
93
+ "noDefaultExport": "error",
94
+ "useImportType": "error",
95
+ "noNonNullAssertion": "error",
96
+ "useEnumInitializers": "off",
97
+ "noParameterAssign": "error"
98
+ },
99
+ "correctness": {
100
+ "noUnusedVariables": "error",
101
+ "noUnusedImports": "error"
102
+ },
103
+ "complexity": {
104
+ "noBannedTypes": "error"
105
+ }
106
+ }
107
+ },
108
+ "formatter": {
109
+ "enabled": true,
110
+ "indentStyle": "space",
111
+ "indentWidth": 2,
112
+ "lineWidth": 100
113
+ },
114
+ "javascript": {
115
+ "formatter": {
116
+ "quoteStyle": "double",
117
+ "semicolons": "asNeeded"
118
+ }
119
+ },
120
+ "files": {
121
+ "ignore": ["node_modules", "dist", "build", ".next", ".nuxt", "coverage"]
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Key Biome rules
127
+
128
+ | Rule | What |
129
+ |---|---|
130
+ | `noExplicitAny` | `any` in annotations is an error |
131
+ | `noNonNullAssertion` | `x!` is an error |
132
+ | `noDefaultExport` | Forces named exports |
133
+ | `useImportType` | Forces `import type` for type-only imports |
134
+ | `noParameterAssign` | No mutation of function parameters |
135
+
136
+ ---
137
+
138
+ ## CI gate
139
+
140
+ ```bash
141
+ bunx biome check .
142
+ bunx tsc --noEmit
143
+ bun test
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Sources
149
+
150
+ - TypeScript: [tsconfig reference](https://www.typescriptlang.org/tsconfig)
151
+ - Biome: [configuration](https://biomejs.dev/reference/configuration/)
152
+ - Total TypeScript: [tsconfig cheat sheet](https://www.totaltypescript.com/tsconfig-cheat-sheet)
@@ -0,0 +1,196 @@
1
+ # Type Patterns
2
+
3
+ How to use TypeScript's type system to catch bugs at compile time.
4
+
5
+ ---
6
+
7
+ ## Branded types — distinct primitives
8
+
9
+ Same runtime type, different meaning. The compiler prevents mixing.
10
+
11
+ ```typescript
12
+ declare const brand: unique symbol
13
+ type Brand<T, B extends string> = T & { readonly [brand]: B }
14
+
15
+ type UserId = Brand<string, "UserId">
16
+ type OrderId = Brand<string, "OrderId">
17
+ type Milliseconds = Brand<number, "Milliseconds">
18
+ type Seconds = Brand<number, "Seconds">
19
+
20
+ function UserId(value: string): UserId { return value as UserId }
21
+ function OrderId(value: string): OrderId { return value as OrderId }
22
+
23
+ function getUser(id: UserId): User { ... }
24
+
25
+ getUser(UserId("abc")) // OK
26
+ getUser(OrderId("abc")) // type error: OrderId is not UserId
27
+ getUser("abc") // type error: string is not UserId
28
+ ```
29
+
30
+ With Zod (preferred at boundaries):
31
+ ```typescript
32
+ import { z } from "zod"
33
+
34
+ const UserIdSchema = z.string().uuid().brand("UserId")
35
+ type UserId = z.infer<typeof UserIdSchema>
36
+ ```
37
+
38
+ **Use when**: IDs, indices, units of measurement — any pair where swapping is a bug.
39
+
40
+ ---
41
+
42
+ ## as const — literal types from values
43
+
44
+ Freezes a value to its narrowest possible type. The foundation for enum-free TypeScript.
45
+
46
+ ```typescript
47
+ const ROLES = ["admin", "user", "guest"] as const
48
+ type Role = (typeof ROLES)[number] // "admin" | "user" | "guest"
49
+
50
+ const HTTP_STATUS = {
51
+ OK: 200,
52
+ NOT_FOUND: 404,
53
+ INTERNAL: 500,
54
+ } as const
55
+ type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS] // 200 | 404 | 500
56
+ ```
57
+
58
+ **Use when**: fixed set of constants. Replaces `enum` entirely.
59
+ **Skip when**: the set is open-ended or user-defined.
60
+
61
+ ---
62
+
63
+ ## satisfies — validate without widening
64
+
65
+ Type-checks a value against a type while preserving the literal type. Best of both worlds.
66
+
67
+ ```typescript
68
+ type Config = Record<string, string | number>
69
+
70
+ // BAD — widens to Record<string, string | number>
71
+ const config: Config = { api: "https://api.example.com", timeout: 30 }
72
+ config.api // string | number — lost the narrowing
73
+
74
+ // GOOD — validates AND preserves literal types
75
+ const config = {
76
+ api: "https://api.example.com",
77
+ timeout: 30,
78
+ } satisfies Config
79
+ config.api // string (narrowed)
80
+ config.timeout // number (narrowed)
81
+ ```
82
+
83
+ **Use when**: you want type validation on a value without losing narrowing.
84
+
85
+ ---
86
+
87
+ ## Discriminated unions — algebraic data types
88
+
89
+ Model every outcome as a type. Force the caller to handle all cases.
90
+
91
+ ```typescript
92
+ type GetUserResult =
93
+ | { readonly kind: "found"; readonly user: User }
94
+ | { readonly kind: "not_found"; readonly id: UserId }
95
+ | { readonly kind: "forbidden"; readonly reason: string }
96
+ ```
97
+
98
+ The `kind` field (or `type`, `status`, `_tag`) is the discriminant. TypeScript narrows on it automatically.
99
+
100
+ ---
101
+
102
+ ## Exhaustive switch — assertNever
103
+
104
+ Every switch on a discriminated union ends with a default that calls `assertNever`.
105
+
106
+ ```typescript
107
+ function assertNever(x: never): never {
108
+ throw new Error(`Unexpected value: ${JSON.stringify(x)}`)
109
+ }
110
+
111
+ function handleResult(result: GetUserResult): string {
112
+ switch (result.kind) {
113
+ case "found":
114
+ return result.user.name
115
+ case "not_found":
116
+ return `No user ${result.id}`
117
+ case "forbidden":
118
+ return `Denied: ${result.reason}`
119
+ default:
120
+ return assertNever(result)
121
+ }
122
+ }
123
+ ```
124
+
125
+ Add a new variant to `GetUserResult`? The compiler errors on the `assertNever` call until you handle it.
126
+
127
+ ---
128
+
129
+ ## Narrowing — let the compiler follow your logic
130
+
131
+ TypeScript narrows types through `typeof`, `instanceof`, `in`, equality checks, and discriminants.
132
+
133
+ ```typescript
134
+ function process(value: string | number | null): string {
135
+ if (value === null) return "nothing"
136
+ // compiler knows: string | number
137
+
138
+ if (typeof value === "string") return value.toUpperCase()
139
+ // compiler knows: number
140
+
141
+ return String(value * 2)
142
+ }
143
+ ```
144
+
145
+ ### Custom type guards
146
+
147
+ ```typescript
148
+ function isNonNull<T>(value: T | null | undefined): value is T {
149
+ return value != null
150
+ }
151
+
152
+ const items = [1, null, 2, undefined, 3]
153
+ const clean = items.filter(isNonNull) // number[]
154
+ ```
155
+
156
+ ---
157
+
158
+ ## import type — separate values from types
159
+
160
+ Always use `import type` for type-only imports. Enforced by `verbatimModuleSyntax`.
161
+
162
+ ```typescript
163
+ import type { User, Config } from "./types" // erased at runtime
164
+ import { createUser } from "./services" // kept at runtime
165
+ ```
166
+
167
+ For mixed imports:
168
+ ```typescript
169
+ import { createUser, type User } from "./users"
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Utility types — quick reference
175
+
176
+ | Need | Use |
177
+ |---|---|
178
+ | All properties readonly | `Readonly<T>` |
179
+ | All properties optional | `Partial<T>` |
180
+ | All properties required | `Required<T>` |
181
+ | Pick specific properties | `Pick<T, "a" \| "b">` |
182
+ | Omit specific properties | `Omit<T, "a" \| "b">` |
183
+ | Key-value map | `Record<K, V>` |
184
+ | Extract from union | `Extract<T, U>` |
185
+ | Exclude from union | `Exclude<T, U>` |
186
+ | Return type of function | `ReturnType<typeof fn>` |
187
+ | Parameters of function | `Parameters<typeof fn>` |
188
+ | Awaited type | `Awaited<Promise<T>>` → `T` |
189
+
190
+ ---
191
+
192
+ ## Sources
193
+
194
+ - TypeScript Handbook: [Narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
195
+ - TypeScript Handbook: [Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html)
196
+ - Total TypeScript: [as const](https://www.totaltypescript.com/as-const)
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env bash
2
+ # No-excuse rule checker for Go files.
3
+ # Mirrors the philosophy of python-programmer / typescript-programmer / rust-programmer scripts:
4
+ # only rules that can be enforced via pure text matching live here.
5
+ # Everything semantic is on golangci-lint + nilaway + go test -race.
6
+
7
+ set -euo pipefail
8
+
9
+ if [ $# -eq 0 ]; then
10
+ echo "Usage: $0 <file.go> [file.go ...]" >&2
11
+ exit 2
12
+ fi
13
+
14
+ violations=0
15
+ report() {
16
+ local file="$1"
17
+ local line="$2"
18
+ local rule="$3"
19
+ local detail="$4"
20
+ echo "::error file=${file},line=${line}::[${rule}] ${detail}" >&2
21
+ violations=$((violations + 1))
22
+ }
23
+
24
+ is_test_file() {
25
+ case "$1" in
26
+ *_test.go) return 0 ;;
27
+ esac
28
+ return 1
29
+ }
30
+
31
+ is_generated_file() {
32
+ local file="$1"
33
+ case "$file" in
34
+ *.pb.go|*.connect.go|*.gen.go) return 0 ;;
35
+ *_string.go) return 0 ;;
36
+ esac
37
+ # First-line check for "Code generated ... DO NOT EDIT." (the official marker)
38
+ if [ -f "$file" ]; then
39
+ head -n 5 "$file" 2>/dev/null | grep -qE "^// Code generated .* DO NOT EDIT\.$" && return 0
40
+ fi
41
+ return 1
42
+ }
43
+
44
+ for file in "$@"; do
45
+ [ -f "$file" ] || continue
46
+ case "$file" in
47
+ *.go) ;;
48
+ *) continue ;;
49
+ esac
50
+
51
+ if is_generated_file "$file"; then
52
+ continue
53
+ fi
54
+
55
+ in_test=0
56
+ if is_test_file "$file"; then
57
+ in_test=1
58
+ fi
59
+
60
+ line_no=0
61
+ while IFS= read -r raw_line || [ -n "$raw_line" ]; do
62
+ line_no=$((line_no + 1))
63
+ line="$raw_line"
64
+
65
+ # Strip line comments before pattern checks
66
+ # (block comments are not handled — keep the rules robust to that limitation).
67
+ code_only="${line%%//*}"
68
+
69
+ # ── Exemption marker: // no-excuse-ok: <reason> ──────────────────
70
+ if [[ "$line" =~ //[[:space:]]*no-excuse-ok:[[:space:]]*.+ ]]; then
71
+ continue
72
+ fi
73
+
74
+ # ── Rule: no `_ = err` (silent error swallow) ────────────────────
75
+ # The errcheck linter catches most of these but the `_ = err` form
76
+ # specifically slips through if used with named returns.
77
+ if [[ "$code_only" =~ ^[[:space:]]*_[[:space:]]*=[[:space:]]*err[[:space:]]*$ ]] ||
78
+ [[ "$code_only" =~ ^[[:space:]]*_[[:space:]]*=[[:space:]]*err[[:space:]]*[^a-zA-Z0-9_].*$ ]]; then
79
+ if [ "$in_test" -eq 0 ]; then
80
+ report "$file" "$line_no" "silent-err" "discarding err with '_ = err' — handle the error"
81
+ fi
82
+ fi
83
+
84
+ # ── Rule: no `panic(` in non-test, non-main code ─────────────────
85
+ # Allowed in main(), allowed in tests, allowed with explicit marker.
86
+ if [[ "$code_only" =~ [^a-zA-Z0-9_]panic\( ]] || [[ "$code_only" =~ ^[[:space:]]*panic\( ]]; then
87
+ if [ "$in_test" -eq 0 ]; then
88
+ # main package main.go is the one exception
89
+ pkg_line=$(head -n 5 "$file" 2>/dev/null | grep -m1 "^package ")
90
+ if [[ "$pkg_line" != "package main" ]]; then
91
+ report "$file" "$line_no" "panic-in-lib" "panic outside main/test — return error instead"
92
+ fi
93
+ fi
94
+ fi
95
+
96
+ # ── Rule: no `log.Fatal` / `log.Panic` in library code ───────────
97
+ if [[ "$code_only" =~ log\.(Fatal|Panic)(f|ln)?\( ]]; then
98
+ if [ "$in_test" -eq 0 ]; then
99
+ pkg_line=$(head -n 5 "$file" 2>/dev/null | grep -m1 "^package ")
100
+ if [[ "$pkg_line" != "package main" ]]; then
101
+ report "$file" "$line_no" "log-fatal-in-lib" "log.Fatal/Panic outside main — return error"
102
+ fi
103
+ fi
104
+ fi
105
+
106
+ # ── Rule: no init() functions ─────────────────────────────────
107
+ # init() ruins testability and creates hidden global state.
108
+ # Exception: //go:build constraint files and generated code.
109
+ if [[ "$code_only" =~ ^func[[:space:]]+init\(\)[[:space:]]*\{ ]]; then
110
+ report "$file" "$line_no" "no-init-func" "init() ruins testability — use explicit constructor"
111
+ fi
112
+
113
+ # ── Rule: no `time.Sleep` in non-test code ──────────────────────
114
+ if [[ "$code_only" =~ time\.Sleep\( ]]; then
115
+ if [ "$in_test" -eq 0 ]; then
116
+ report "$file" "$line_no" "time-sleep" "time.Sleep in production code — use ticker/timer with ctx"
117
+ fi
118
+ fi
119
+
120
+ # ── Rule: no `context.Background()` inside functions (only in main/init/test) ──
121
+ if [[ "$code_only" =~ context\.Background\(\) ]]; then
122
+ if [ "$in_test" -eq 0 ]; then
123
+ pkg_line=$(head -n 5 "$file" 2>/dev/null | grep -m1 "^package ")
124
+ if [[ "$pkg_line" != "package main" ]]; then
125
+ report "$file" "$line_no" "ctx-background-in-lib" "context.Background() outside main — propagate ctx as parameter"
126
+ fi
127
+ fi
128
+ fi
129
+
130
+ # ── Rule: no `interface{}` (use `any`, the alias from Go 1.18+) ──
131
+ if [[ "$code_only" =~ interface\{\} ]]; then
132
+ report "$file" "$line_no" "old-interface-empty" "use 'any' instead of 'interface{}' (Go 1.18+)"
133
+ fi
134
+
135
+ # ── Rule: no bare `fmt.Println` for logging (use slog) ───────────
136
+ # Acceptable in main.go (CLI output) and tests. Reject in libraries.
137
+ if [[ "$code_only" =~ fmt\.(Print|Println|Printf)\( ]]; then
138
+ if [ "$in_test" -eq 0 ]; then
139
+ pkg_line=$(head -n 5 "$file" 2>/dev/null | grep -m1 "^package ")
140
+ if [[ "$pkg_line" != "package main" ]]; then
141
+ report "$file" "$line_no" "fmt-print-in-lib" "fmt.Print* in library — use slog for structured logs"
142
+ fi
143
+ fi
144
+ fi
145
+
146
+ # ── Rule: no `nolint` directive without reason ───────────────────
147
+ if [[ "$line" =~ //nolint(:|$| ) ]]; then
148
+ if ! [[ "$line" =~ //nolint:[a-zA-Z0-9_,-]+[[:space:]]+//[[:space:]]*[^[:space:]] ]]; then
149
+ report "$file" "$line_no" "nolint-no-reason" "//nolint requires a // reason after the linter list"
150
+ fi
151
+ fi
152
+
153
+ # ── Rule: no TODO / FIXME without an issue link or owner ─────────
154
+ # Check the full line — TODOs live in comments, which $code_only has stripped.
155
+ if echo "$line" | grep -qE '(TODO|FIXME|XXX)([[:space:]]|:)'; then
156
+ if ! echo "$line" | grep -qE '(TODO|FIXME|XXX).*[(@[]'; then
157
+ report "$file" "$line_no" "todo-no-owner" "TODO/FIXME requires (#issue) or @owner attribution"
158
+ fi
159
+ fi
160
+ done < "$file"
161
+ done
162
+
163
+ if [ "$violations" -gt 0 ]; then
164
+ echo "" >&2
165
+ echo "go-programmer: $violations violation(s). Run also:" >&2
166
+ echo " gofumpt -l ." >&2
167
+ echo " golangci-lint run --timeout 5m ./..." >&2
168
+ echo " nilaway ./..." >&2
169
+ echo " go test -race -shuffle=on -count=1 ./..." >&2
170
+ exit 1
171
+ fi
172
+
173
+ echo "go-programmer: no-excuse rules passed for $# file(s)."
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = [
5
+ # "typer",
6
+ # "rich",
7
+ # ]
8
+ # ///
9
+
10
+ # ─── How to run ───
11
+ # 1. Install uv (if not installed):
12
+ # curl -LsSf https://astral.sh/uv/install.sh | sh
13
+ # 2. Run:
14
+ # uv run new-project.py myservice
15
+ # uv run new-project.py myservice --module github.com/your-org/myservice
16
+ # ──────────────────
17
+ #
18
+ # Creates a new Go project with the canonical strict layout:
19
+ # - go.mod with go 1.23
20
+ # - .golangci.yml (v2, strict bundle)
21
+ # - Taskfile.yml (fmt + lint + test + build)
22
+ # - cmd/server/main.go entrypoint
23
+ # - internal/{cmd,config,api,domain,obs} skeletons
24
+ # - .github/workflows/ci.yml
25
+ #
26
+ # Templates live in ./templates/ — keep this script under 250 pure LOC.
27
+
28
+ from __future__ import annotations
29
+
30
+ import subprocess
31
+ import sys
32
+ from pathlib import Path
33
+ from string import Template
34
+
35
+ import typer
36
+ from rich.console import Console
37
+
38
+ console = Console(stderr=True)
39
+
40
+ TEMPLATES_DIR = Path(__file__).parent / "templates"
41
+
42
+
43
+ def _render(template_file: str, **subs: str) -> str:
44
+ """Read a template file and apply $placeholder substitutions.
45
+
46
+ Uses string.Template ($name) so Go/YAML curly braces stay literal.
47
+ """
48
+ raw = (TEMPLATES_DIR / template_file).read_text()
49
+ if not subs:
50
+ return raw
51
+ return Template(raw).substitute(**subs)
52
+
53
+
54
+ # (template-file → relative output path; is_format = .format() is run)
55
+ FILES: list[tuple[str, str, bool]] = [
56
+ (".golangci.yml", ".golangci.yml", False),
57
+ ("Taskfile.yml", "Taskfile.yml", False),
58
+ (".editorconfig", ".editorconfig", False),
59
+ ("gitignore", ".gitignore", False),
60
+ ("ci.yml", ".github/workflows/ci.yml", False),
61
+ ("run.go", "internal/cmd/run.go", False),
62
+ ("config.go", "internal/config/config.go", False),
63
+ ("main.go.tmpl", "cmd/server/main.go", True),
64
+ ("AGENTS.md.tmpl", "AGENTS.md", True),
65
+ ("README.md.tmpl", "README.md", True),
66
+ ]
67
+
68
+
69
+ def _init_go_module(project_dir: Path, module: str) -> None:
70
+ try:
71
+ subprocess.run(
72
+ ["go", "mod", "init", module],
73
+ cwd=project_dir,
74
+ check=True,
75
+ capture_output=True,
76
+ )
77
+ console.print(f" [dim]ran[/] go mod init {module}")
78
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
79
+ console.print(f" [yellow]warn[/] go mod init failed ({e}); writing fallback go.mod")
80
+ (project_dir / "go.mod").write_text(f"module {module}\n\ngo 1.23\n")
81
+
82
+
83
+ def _create_layout(project_dir: Path) -> None:
84
+ """Create the canonical internal/ tree."""
85
+ subdirs = [
86
+ "cmd/server",
87
+ "internal/cmd",
88
+ "internal/config",
89
+ "internal/api",
90
+ "internal/domain",
91
+ "internal/obs",
92
+ ".github/workflows",
93
+ ]
94
+ for sd in subdirs:
95
+ (project_dir / sd).mkdir(parents=True)
96
+
97
+
98
+ def _write_files(project_dir: Path, name: str, module: str, purpose: str) -> None:
99
+ """Render every template into the project tree."""
100
+ for tmpl_name, out_rel, is_format in FILES:
101
+ subs = (
102
+ {"name": name, "module": module, "short_purpose": purpose}
103
+ if is_format
104
+ else {}
105
+ )
106
+ content = _render(tmpl_name, **subs)
107
+ out_path = project_dir / out_rel
108
+ out_path.parent.mkdir(parents=True, exist_ok=True)
109
+ out_path.write_text(content)
110
+ console.print(f" [dim]wrote[/] {out_rel}")
111
+
112
+
113
+ def main(
114
+ name: str,
115
+ path: str = typer.Option(".", help="Parent dir"),
116
+ module: str = typer.Option("", help="Go module path; default: <name>"),
117
+ purpose: str = typer.Option("HTTP", help="Short purpose for AGENTS.md"),
118
+ ) -> None:
119
+ """Scaffold a new Go project with the strict toolchain."""
120
+ project_dir = Path(path) / name
121
+ if project_dir.exists():
122
+ console.print(f"[red]✗[/red] {project_dir} already exists")
123
+ sys.exit(1)
124
+
125
+ module_path = module or name
126
+
127
+ project_dir.mkdir(parents=True)
128
+ _create_layout(project_dir)
129
+ _init_go_module(project_dir, module_path)
130
+ _write_files(project_dir, name, module_path, purpose)
131
+
132
+ console.print(f"\n[bold green]Done![/] cd {project_dir}")
133
+ console.print(" go get github.com/caarlos0/env/v11")
134
+ console.print(" task # fmt + lint + test")
135
+
136
+
137
+ if __name__ == "__main__":
138
+ typer.run(main)
@@ -0,0 +1,13 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 4
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.{yml,yaml,json,md}]
12
+ indent_style = space
13
+ indent_size = 2