@dowel/dowel 0.3.0 → 0.3.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 +83 -30
- package/bin/commands/setup.mjs +11 -4
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,12 +1,72 @@
|
|
|
1
1
|
# dowel
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
**A deterministic harness for coding agents.** dowel compiles your
|
|
4
|
+
architecture into rules and checks the *whole codebase* on every run, so an AI
|
|
5
|
+
agent — or a human — gets the same non-negotiable feedback every time: what's
|
|
6
|
+
allowed, what isn't, and *why*.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npm i -D @dowel/dowel && npx dowel setup
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
Coding agents amplify whatever patterns already exist in a repo — including the
|
|
17
|
+
uneven ones. Boundaries with exceptions get copied as exceptions; the codebase
|
|
18
|
+
drifts. The usual answer is more prose — `CLAUDE.md`, `AGENTS.md`, `.cursorrules`
|
|
19
|
+
— but prose is **non-deterministic**: the model may read it, may not, and it
|
|
20
|
+
rots as the codebase grows.
|
|
21
|
+
|
|
22
|
+
> *"Agents write the code; linters write the law. Agents iterate against
|
|
23
|
+
> deterministic feedback far better than they iterate against vibes."*
|
|
24
|
+
|
|
25
|
+
dowel moves the gate **into the structure of the codebase**. Architecture
|
|
26
|
+
becomes compiled, checkable law:
|
|
27
|
+
|
|
28
|
+
- **Deterministic** — the same diagnostics every run, not a suggestion the model
|
|
29
|
+
weighs. Exit code is non-zero on any `error`, so it gates CI and agent loops.
|
|
30
|
+
- **Whole-codebase, every time** — no context window to overflow, no
|
|
31
|
+
`AGENTS.md` to keep in sync. The rules *are* the spec.
|
|
32
|
+
- **Diagnostics + intent** — every finding states the rule's intent, so the
|
|
33
|
+
agent reading it learns the pattern and fixes it, then carries the pattern
|
|
34
|
+
forward. The codebase teaches the next agent.
|
|
35
|
+
|
|
36
|
+
## What it checks that a type-checker can't
|
|
37
|
+
|
|
38
|
+
- **Architecture boundaries** — vertical-slice structure, cross-slice imports,
|
|
39
|
+
layer purity (no SQL in services, no `Response` in repos, Result at write
|
|
40
|
+
boundaries), folder vocabulary, ownership.
|
|
41
|
+
- **Raw SQL against a real schema** — dowel builds a **shadow database** from
|
|
42
|
+
your migrations (Postgres *or* SQLite/D1) and `PREPARE`s every query against
|
|
43
|
+
it, catching drift, missing indexes, and N+1s a type-checker never sees. If
|
|
44
|
+
the shadow is unreachable it says so, loudly — never a silent pass.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm i -D @dowel/dowel
|
|
50
|
+
npx dowel setup # detects stack/DB/structure, picks a profile,
|
|
51
|
+
# writes arch-lint.toml, scaffolds rules/
|
|
52
|
+
npm run lint:arch # or: npx dowel
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`dowel setup` reads your repo and recommends a **profile** — a curated rule set
|
|
56
|
+
for your stack:
|
|
57
|
+
|
|
58
|
+
| Profile | For |
|
|
59
|
+
|---------|-----|
|
|
60
|
+
| `node-postgres` | Node/Hono + Postgres |
|
|
61
|
+
| `cloudflare-d1` | Cloudflare Workers + D1 (SQLite) |
|
|
62
|
+
| `sanity-nextjs` | Next.js + Sanity (no SQL) |
|
|
63
|
+
| `generic` / `portable` | any TypeScript / any language baseline |
|
|
64
|
+
|
|
65
|
+
## Authoring rules
|
|
66
|
+
|
|
67
|
+
Portable rules ship in the packs. Codebase-specific rules live *with your
|
|
68
|
+
codebase* as TypeScript — type-checked by your own `tsc`, scaffolded by
|
|
69
|
+
`dowel new-rule`:
|
|
10
70
|
|
|
11
71
|
```ts
|
|
12
72
|
// rules/no-sql-outside-repos.ts
|
|
@@ -15,41 +75,34 @@ import { defineRule } from "@dowel/dowel";
|
|
|
15
75
|
export default defineRule({
|
|
16
76
|
id: "NO_SQL_OUTSIDE_REPOS",
|
|
17
77
|
intent: "raw SQL lives only in repos/ — services compose, repos query",
|
|
18
|
-
check(project) {
|
|
19
|
-
return project
|
|
20
|
-
.files()
|
|
78
|
+
check(project, ctx) {
|
|
79
|
+
return project.files()
|
|
21
80
|
.filter((f) => /\bselect\b/i.test(f.text) && !f.relPath.includes("/repos/"))
|
|
22
|
-
.map((f) => ({ severity: "error", file: f.path, line: 1,
|
|
81
|
+
.map((f) => ({ severity: "error", file: f.path, line: 1,
|
|
82
|
+
message: "move SQL into a repo" }));
|
|
23
83
|
},
|
|
24
84
|
});
|
|
25
85
|
```
|
|
26
86
|
|
|
27
|
-
## Install
|
|
28
|
-
|
|
29
87
|
```bash
|
|
30
|
-
|
|
88
|
+
dowel new-rule NO_SQL_OUTSIDE_REPOS # scaffolds the rule + passing/violation fixtures
|
|
31
89
|
```
|
|
32
90
|
|
|
33
|
-
|
|
34
|
-
(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Use
|
|
38
|
-
|
|
39
|
-
Add an `arch-lint.toml` and a `rules/` dir, then:
|
|
91
|
+
Every rule declares **when it fires** (scope — e.g. exempt tests), **which
|
|
92
|
+
variant** (typed options), and is toggled per-repo in `arch-lint.toml` — so
|
|
93
|
+
calibration is data, not a fork of the rule.
|
|
40
94
|
|
|
41
|
-
|
|
42
|
-
// package.json
|
|
43
|
-
"scripts": { "lint:arch": "dowel" }
|
|
44
|
-
```
|
|
95
|
+
## How it's built
|
|
45
96
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
97
|
+
The Rust engine ships as a prebuilt native Node addon: `npm i` pulls exactly one
|
|
98
|
+
`@dowel/dowel-<platform>` package via `optionalDependencies` — no Rust
|
|
99
|
+
toolchain, no post-install compile, no committed binary. Borrows hard-won
|
|
100
|
+
patterns from `ruff`/`oxc` (speed), `sqlc` (DB-checked SQL), and `Squawk`
|
|
101
|
+
(migration safety).
|
|
50
102
|
|
|
51
|
-
|
|
103
|
+
> **Platform support:** `darwin-arm64` ships today; the Linux/Windows binaries
|
|
104
|
+
> land via the CI build matrix. On an unsupported platform the loader tells you.
|
|
52
105
|
|
|
53
106
|
## License
|
|
54
107
|
|
|
55
|
-
MIT
|
|
108
|
+
MIT OR Apache-2.0
|
package/bin/commands/setup.mjs
CHANGED
|
@@ -114,12 +114,16 @@ function detectArchitecture(root, featuresRoot) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// NOTE: we deliberately DO NOT derive slice_vocab from the folder names found
|
|
118
|
+
// inside slices — on a real repo that yields garbage (component/util names
|
|
119
|
+
// like ConditionEditor, calculate-score, …) rather than the canonical VSA
|
|
120
|
+
// buckets. The generated config omits slice_vocab so the vsa pack's default
|
|
121
|
+
// bucket vocabulary applies (contracts/public/api/services/repos/types/
|
|
122
|
+
// components/routes/hooks/workflow/client.ts). A consumer adds a genuine
|
|
123
|
+
// recurring bucket by hand if needed.
|
|
119
124
|
return {
|
|
120
125
|
featuresRoot,
|
|
121
126
|
groups: [...groups].sort(),
|
|
122
|
-
sliceVocab: [...vocab].sort(),
|
|
123
127
|
sliceCount: sliceDirs.length,
|
|
124
128
|
};
|
|
125
129
|
}
|
|
@@ -231,7 +235,10 @@ function renderConfig({ profile, dialect, arch, prefixes, migrationsDir }) {
|
|
|
231
235
|
L.push("[architecture]");
|
|
232
236
|
L.push(`features_root = "${arch.featuresRoot}"`);
|
|
233
237
|
if (arch.groups.length) L.push(`groups = ${tomlArray(arch.groups)}`);
|
|
234
|
-
|
|
238
|
+
// slice_vocab intentionally omitted — the vsa pack default applies. Setting it
|
|
239
|
+
// from detected folder names produced garbage; declare it by hand only if your
|
|
240
|
+
// slices genuinely use a different bucket vocabulary.
|
|
241
|
+
L.push("# slice_vocab = [...] # omitted: uses the vsa pack default bucket vocab");
|
|
235
242
|
if (prefixes.length) L.push(`table_prefixes = ${tomlArray(prefixes)}`);
|
|
236
243
|
L.push("");
|
|
237
244
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dowel/dowel",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "dowel — a compiled, opinionated architecture linter. TS-native rule authoring over a Rust engine (Project, AST, shadow-DB SQL oracle).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"@types/node": "^22.0.0"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
|
-
"@dowel/dowel-darwin-arm64": "0.3.
|
|
63
|
-
"@dowel/dowel-darwin-x64": "0.3.
|
|
64
|
-
"@dowel/dowel-linux-x64-gnu": "0.3.
|
|
65
|
-
"@dowel/dowel-linux-arm64-gnu": "0.3.
|
|
66
|
-
"@dowel/dowel-linux-x64-musl": "0.3.
|
|
67
|
-
"@dowel/dowel-win32-x64-msvc": "0.3.
|
|
62
|
+
"@dowel/dowel-darwin-arm64": "0.3.1",
|
|
63
|
+
"@dowel/dowel-darwin-x64": "0.3.1",
|
|
64
|
+
"@dowel/dowel-linux-x64-gnu": "0.3.1",
|
|
65
|
+
"@dowel/dowel-linux-arm64-gnu": "0.3.1",
|
|
66
|
+
"@dowel/dowel-linux-x64-musl": "0.3.1",
|
|
67
|
+
"@dowel/dowel-win32-x64-msvc": "0.3.1"
|
|
68
68
|
},
|
|
69
69
|
"publishConfig": {
|
|
70
70
|
"access": "public"
|