@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 CHANGED
@@ -1,12 +1,72 @@
1
1
  # dowel
2
2
 
3
- A compiled, opinionated **architecture linter**. Rules surface diagnostics +
4
- intent; the agent reading them applies the fix. Unlike a type-checker it also
5
- validates raw SQL against a real schema it builds a shadow Postgres from your
6
- migrations and `PREPARE`s every query against it.
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 humangets the same non-negotiable feedback every time: what's
6
+ allowed, what isn't, and *why*.
7
7
 
8
- This package ships the Rust engine as a native Node addon plus a TypeScript
9
- authoring API, so you write your codebase's architecture rules in TypeScript:
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, message: "move SQL into a repo" }));
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
- npm i -D @dowel/dowel
88
+ dowel new-rule NO_SQL_OUTSIDE_REPOS # scaffolds the rule + passing/violation fixtures
31
89
  ```
32
90
 
33
- npm pulls exactly one prebuilt native package for your platform
34
- (`@dowel/dowel-<triple>`) via `optionalDependencies` — no Rust toolchain, no
35
- post-install compile, no committed binary.
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
- ```jsonc
42
- // package.json
43
- "scripts": { "lint:arch": "dowel" }
44
- ```
95
+ ## How it's built
45
96
 
46
- ```bash
47
- npx dowel # lint cwd against ./arch-lint.toml + ./rules
48
- dowel --root ../ --config arch-lint.toml --rules rules
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
- Exit code is non-zero on any `error`-severity diagnostic, so it gates CI.
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
@@ -114,12 +114,16 @@ function detectArchitecture(root, featuresRoot) {
114
114
  }
115
115
  }
116
116
  }
117
- const vocab = new Set();
118
- for (const slice of sliceDirs) for (const b of subdirs(slice)) vocab.add(baseName(b));
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
- if (arch.sliceVocab.length) L.push(`slice_vocab = ${tomlArray(arch.sliceVocab)}`);
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.0",
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.0",
63
- "@dowel/dowel-darwin-x64": "0.3.0",
64
- "@dowel/dowel-linux-x64-gnu": "0.3.0",
65
- "@dowel/dowel-linux-arm64-gnu": "0.3.0",
66
- "@dowel/dowel-linux-x64-musl": "0.3.0",
67
- "@dowel/dowel-win32-x64-msvc": "0.3.0"
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"