@konvert7/klint 0.23.0 → 0.24.1
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.
- package/README.md +32 -1
- package/package.json +5 -5
- package/skill/klint-rules/SKILL.md +47 -5
- package/tools/generate-homebrew-formula.ts +64 -0
package/README.md
CHANGED
|
@@ -8,7 +8,14 @@ Use klint when "please follow AGENTS.md" is not strong enough. Put the rule in `
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
For macOS native CLI usage:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
brew tap konvert7/tap
|
|
15
|
+
brew install klint
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For TypeScript projects using Bun:
|
|
12
19
|
|
|
13
20
|
```sh
|
|
14
21
|
bun add -d @konvert7/klint
|
|
@@ -90,6 +97,7 @@ klint is for rules that are too project-specific or context-heavy for a formatte
|
|
|
90
97
|
| Layer boundaries | `src/components/**` must not import from `src/data/**` |
|
|
91
98
|
| Singleton ownership | `process.env.API_KEY` may only appear in `src/lib/auth.ts` |
|
|
92
99
|
| Forbidden patterns | `console.log(` is blocked inside hook libraries |
|
|
100
|
+
| Raw JSX elements | `<button>` is blocked outside `src/components/ui/**` |
|
|
93
101
|
| Type-aware mistakes | Promise-returning calls cannot be silently discarded |
|
|
94
102
|
| Repository policy | Custom `klint.rules.ts` rules run with the same severity/config system |
|
|
95
103
|
|
|
@@ -266,6 +274,18 @@ arch:
|
|
|
266
274
|
message: "Libraries should return or throw, not terminate the process"
|
|
267
275
|
```
|
|
268
276
|
|
|
277
|
+
Block raw JSX/HTML elements with `jsx-element` to push consumers onto design-system primitives. It is AST-matched on intrinsic (lowercase) tag names, so it is robust to whitespace, attributes, and lookalikes like `<buttonGroup>`:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
arch:
|
|
281
|
+
forbidden:
|
|
282
|
+
- jsx-element: ["button", "input", "label"]
|
|
283
|
+
in: ["src/app/**/*.tsx", "src/components/**/*.tsx", "!src/components/ui/**"]
|
|
284
|
+
message: "Use the design-system primitives in @/components/ui/* instead of raw HTML elements"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`jsx-element` matches intrinsic HTML elements only — not custom components like `<Button>`. Use `arch.imports` to restrict those.
|
|
288
|
+
|
|
269
289
|
### Singleton Locations
|
|
270
290
|
|
|
271
291
|
Enforce that a pattern appears only in one designated file:
|
|
@@ -279,6 +299,17 @@ arch:
|
|
|
279
299
|
message: "Use the auth module"
|
|
280
300
|
```
|
|
281
301
|
|
|
302
|
+
`singleton` accepts `jsx-element` too — pin a raw element to the one primitive file that is allowed to render it:
|
|
303
|
+
|
|
304
|
+
```yaml
|
|
305
|
+
arch:
|
|
306
|
+
singleton:
|
|
307
|
+
- jsx-element: "button"
|
|
308
|
+
only: "src/components/ui/button.tsx"
|
|
309
|
+
in: ["src/**/*.tsx"]
|
|
310
|
+
message: "Raw <button> belongs only to the Button primitive"
|
|
311
|
+
```
|
|
312
|
+
|
|
282
313
|
### Python Architecture Checks
|
|
283
314
|
|
|
284
315
|
The Rust engine can apply architecture rules to Python projects:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konvert7/klint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Architecture-as-Code linter for TypeScript. Enforce layer boundaries, import rules, and singleton patterns in YAML.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"zod": "^4.4.3"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"@konvert7/klint-darwin-arm64": "0.
|
|
68
|
-
"@konvert7/klint-darwin-x64": "0.
|
|
69
|
-
"@konvert7/klint-linux-x64": "0.
|
|
70
|
-
"@konvert7/klint-win32-x64": "0.
|
|
67
|
+
"@konvert7/klint-darwin-arm64": "0.24.1",
|
|
68
|
+
"@konvert7/klint-darwin-x64": "0.24.1",
|
|
69
|
+
"@konvert7/klint-linux-x64": "0.24.1",
|
|
70
|
+
"@konvert7/klint-win32-x64": "0.24.1"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@biomejs/biome": "2.4.7",
|
|
@@ -4,6 +4,19 @@ description: Add, modify, or explain klint architecture rules for this repo. Use
|
|
|
4
4
|
argument-hint: <constraint to enforce>
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Schema
|
|
8
|
+
|
|
9
|
+
The full, authoritative config schema lives in the repo and is the source of truth for every field below:
|
|
10
|
+
|
|
11
|
+
- YAML: <https://github.com/konvert7/klint/blob/main/klint.schema.yaml>
|
|
12
|
+
- JSON: <https://github.com/konvert7/klint/blob/main/klint.schema.json>
|
|
13
|
+
|
|
14
|
+
Wire it into `klint.yaml` for editor autocomplete and validation:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
# yaml-language-server: $schema=./klint.schema.yaml
|
|
18
|
+
```
|
|
19
|
+
|
|
7
20
|
## Workflow
|
|
8
21
|
|
|
9
22
|
1. **Understand the constraint** — if scope is ambiguous, ask: which files/layers are involved? should it block or warn? are there legitimate exceptions?
|
|
@@ -19,8 +32,10 @@ argument-hint: <constraint to enforce>
|
|
|
19
32
|
|---|---|
|
|
20
33
|
| Layer X must not import from layer Y | `arch.imports` + `deny` |
|
|
21
34
|
| Layer X may only import from Y and Z | `arch.imports` + `allow` |
|
|
22
|
-
| Pattern P must only appear in one designated file | `arch.singleton` |
|
|
23
|
-
| Pattern P must never appear inside a scoped layer | `arch.forbidden` |
|
|
35
|
+
| Pattern P must only appear in one designated file | `arch.singleton` + `pattern` |
|
|
36
|
+
| Pattern P must never appear inside a scoped layer | `arch.forbidden` + `pattern` |
|
|
37
|
+
| Raw HTML/JSX element must never appear in a scoped layer | `arch.forbidden` + `jsx-element` |
|
|
38
|
+
| Raw HTML/JSX element may only appear in one primitive file | `arch.singleton` + `jsx-element` |
|
|
24
39
|
|
|
25
40
|
4. **Read `klint.yaml`** — check existing `arch.layers` and rules before adding anything. Add a new named layer to `arch.layers` if the file group doesn't exist yet.
|
|
26
41
|
|
|
@@ -69,29 +84,56 @@ arch:
|
|
|
69
84
|
```
|
|
70
85
|
|
|
71
86
|
### Singleton — one designated location
|
|
87
|
+
|
|
88
|
+
Pin a `pattern` (literal string) **or** a `jsx-element` (AST-matched tag) to exactly one file. Every other in-scope occurrence is a violation.
|
|
89
|
+
|
|
72
90
|
```yaml
|
|
73
91
|
arch:
|
|
74
92
|
singleton:
|
|
93
|
+
# pattern: literal string match — fires if it appears anywhere except `only`
|
|
75
94
|
- pattern: "process.env.API_KEY"
|
|
76
95
|
only: "src/lib/auth.ts"
|
|
77
|
-
in: ["src/**"] # optional: limit scan scope (default: all files)
|
|
96
|
+
in: ["src/**"] # optional: limit scan scope (default: all files). string OR array
|
|
78
97
|
message: "Use the auth module instead of reading API_KEY directly"
|
|
98
|
+
severity: error # optional — default error; use warn to record without blocking
|
|
99
|
+
|
|
100
|
+
# jsx-element: pin a raw element to its design-system primitive
|
|
101
|
+
- jsx-element: "button" # one tag, or a list: ["button", "input"]
|
|
102
|
+
only: "src/components/ui/button.tsx"
|
|
103
|
+
in: ["src/**/*.tsx"]
|
|
104
|
+
message: "Raw <button> belongs only to the Button primitive"
|
|
79
105
|
```
|
|
80
106
|
|
|
81
|
-
`
|
|
107
|
+
Required: `only` + `message`, plus one of `pattern` / `jsx-element` (not both in the same stanza).
|
|
82
108
|
|
|
83
109
|
### Forbidden — banned pattern in scope
|
|
110
|
+
|
|
111
|
+
Block a `pattern` (literal string) **or** a `jsx-element` (AST-matched tag) inside a scoped layer.
|
|
112
|
+
|
|
84
113
|
```yaml
|
|
85
114
|
arch:
|
|
86
115
|
forbidden:
|
|
116
|
+
# pattern: literal substring — no regex
|
|
87
117
|
- pattern: "console.log("
|
|
88
|
-
in: ["src/lib/**"]
|
|
118
|
+
in: ["src/lib/**"] # string OR array; supports ! negation
|
|
89
119
|
message: "Use the logger — console.log leaks into the agent event stream"
|
|
120
|
+
severity: error # optional — default error; use warn to record without blocking
|
|
121
|
+
|
|
122
|
+
# jsx-element: forbid raw HTML elements outside the design system
|
|
123
|
+
- jsx-element: ["button", "input", "label"]
|
|
124
|
+
in: ["src/app/**/*.tsx", "src/components/**/*.tsx", "!src/components/ui/**"]
|
|
125
|
+
message: "Use the design-system primitives in @/components/ui/* instead of raw HTML elements"
|
|
90
126
|
```
|
|
91
127
|
|
|
128
|
+
Required: `in` + `message`, plus one of `pattern` / `jsx-element` (not both in the same stanza).
|
|
129
|
+
|
|
130
|
+
`jsx-element` matches intrinsic element names on the AST (opening and self-closing tags), so it is robust to whitespace, attributes, and naming collisions like `<buttonGroup>` — unlike a literal `pattern: "<button"` scan. It works on `.tsx`/`.jsx` files in both the TS and Rust engines.
|
|
131
|
+
|
|
92
132
|
## Pitfalls
|
|
93
133
|
|
|
94
134
|
- `pattern` in `singleton` and `forbidden` is a **literal string** — will not catch `process.env["KEY"]` (bracket notation). Grep for both forms if both are possible.
|
|
135
|
+
- `jsx-element` matches **intrinsic** (lowercase HTML) tags only — `button`, `input`, `label`. It does not match custom React components like `<Button>`; to restrict those, use `arch.imports` against the component's module instead.
|
|
136
|
+
- A `forbidden`/`singleton` stanza takes either `pattern` or `jsx-element`, never both — split into two stanzas if you need both.
|
|
95
137
|
- `only` in `singleton` is relative to the project root — use forward slashes on all platforms.
|
|
96
138
|
- `severity: warn` records the violation but does not block the session. Use `error` (the default) to block.
|
|
97
139
|
- Path aliases (`@/*`) resolve via `tsconfig.json` `compilerOptions.paths`, including `extends` chains.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
interface FormulaOptions {
|
|
5
|
+
version: string;
|
|
6
|
+
darwinArm64Sha: string;
|
|
7
|
+
darwinX64Sha: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function homebrewFormula(options: FormulaOptions): string {
|
|
11
|
+
const version = options.version.replace(/^v/, "");
|
|
12
|
+
|
|
13
|
+
return `# typed: false
|
|
14
|
+
# frozen_string_literal: true
|
|
15
|
+
|
|
16
|
+
class Klint < Formula
|
|
17
|
+
desc "Architecture-as-Code linter for TypeScript, Python, and Swift projects"
|
|
18
|
+
homepage "https://github.com/konvert7/klint"
|
|
19
|
+
version "${version}"
|
|
20
|
+
license "MIT"
|
|
21
|
+
|
|
22
|
+
if Hardware::CPU.arm?
|
|
23
|
+
url "https://github.com/konvert7/klint/releases/download/native-v${version}/klint-${version}-darwin-arm64.tar.gz"
|
|
24
|
+
sha256 "${options.darwinArm64Sha}"
|
|
25
|
+
else
|
|
26
|
+
url "https://github.com/konvert7/klint/releases/download/native-v${version}/klint-${version}-darwin-x64.tar.gz"
|
|
27
|
+
sha256 "${options.darwinX64Sha}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def install
|
|
31
|
+
binary = File.exist?("klint") ? "klint" : Dir["klint-*/klint"].first
|
|
32
|
+
odie "klint binary not found" unless binary
|
|
33
|
+
bin.install binary => "klint"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
test do
|
|
37
|
+
assert_match "klint-rs", shell_output("#{bin}/klint --version")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (import.meta.main) {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const output = requireValue(args, "--output");
|
|
46
|
+
const formula = homebrewFormula({
|
|
47
|
+
version: requireValue(args, "--version"),
|
|
48
|
+
darwinArm64Sha: requireValue(args, "--darwin-arm64-sha"),
|
|
49
|
+
darwinX64Sha: requireValue(args, "--darwin-x64-sha"),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const path = resolve(output);
|
|
53
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
54
|
+
writeFileSync(path, formula);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function requireValue(args: string[], name: string): string {
|
|
58
|
+
const index = args.indexOf(name);
|
|
59
|
+
const value = index >= 0 ? args[index + 1] : undefined;
|
|
60
|
+
if (!value) {
|
|
61
|
+
throw new Error(`${name} is required`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|