arkgate 2.1.0
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/CHANGELOG.md +1249 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/SECURITY.md +39 -0
- package/bin/ark-check.mjs +5204 -0
- package/bin/ark-mcp.mjs +898 -0
- package/bin/ark-shared.mjs +1520 -0
- package/bin/ark.mjs +491 -0
- package/dist/eslint/index.cjs +222 -0
- package/dist/eslint/index.cjs.map +1 -0
- package/dist/eslint/index.d.cts +42 -0
- package/dist/eslint/index.d.ts +40 -0
- package/dist/eslint/index.js +193 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/index.cjs +3080 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +577 -0
- package/dist/index.d.ts +577 -0
- package/dist/index.js +2998 -0
- package/dist/index.js.map +1 -0
- package/dist/nestjs/index.cjs +2332 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +22 -0
- package/dist/nestjs/index.js +2308 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/types-DpdVN7Lm.d.cts +1023 -0
- package/dist/types-DpdVN7Lm.d.ts +1023 -0
- package/docs/agent-guide.md +490 -0
- package/docs/ai-gates.md +337 -0
- package/docs/ark-check-example.json +87 -0
- package/docs/assets/ark-write-gate.svg +28 -0
- package/docs/brownfield-adoption.md +87 -0
- package/docs/demos/01-write-gate-self-correction.md +74 -0
- package/docs/demos/02-brownfield-baseline-adoption.md +71 -0
- package/docs/demos/03-copilot-autopilot.md +83 -0
- package/docs/enthusiast/README.md +62 -0
- package/docs/enthusiast/explanation-application-shape.md +29 -0
- package/docs/enthusiast/how-to-agent-gates.md +36 -0
- package/docs/enthusiast/how-to-gallery-starter.md +27 -0
- package/docs/enthusiast/how-to-pick-shape.md +45 -0
- package/docs/enthusiast/how-to-policy-pack.md +37 -0
- package/docs/enthusiast/reference-archetypes.md +36 -0
- package/docs/enthusiast/reference-commands.md +50 -0
- package/docs/enthusiast/tutorial-first-project.md +86 -0
- package/docs/production-hardening.md +59 -0
- package/package.json +125 -0
- package/server.json +39 -0
- package/templates/architecture-playbook.json +339 -0
- package/templates/policy-packs/enthusiast-feature-sliced.json +20 -0
- package/templates/policy-packs/enthusiast-hexagonal.json +18 -0
- package/templates/policy-packs/enthusiast-layered.json +18 -0
- package/templates/policy-packs/enthusiast-monorepo.json +18 -0
- package/templates/skills/ark-adopt.md +103 -0
- package/templates/skills/ark-architect.md +90 -0
- package/templates/skills/ark-autopilot.md +95 -0
- package/templates/skills/ark-contract.md +98 -0
- package/templates/skills/ark-coverage.md +96 -0
- package/templates/skills/ark-explain.md +78 -0
- package/templates/skills/ark-fix.md +96 -0
- package/templates/skills/ark-loop.md +69 -0
- package/templates/skills/ark-place.md +68 -0
- package/templates/skills/ark-runtime.md +62 -0
- package/templates/skills/ark-upgrade.md +109 -0
package/docs/ai-gates.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Gating AI Agents with Ark
|
|
2
|
+
|
|
3
|
+
**Ark** is the architecture co-pilot for AI TypeScript (write gate · CI · plan/loop).
|
|
4
|
+
The write-path gate is what makes it different from every other architecture linter:
|
|
5
|
+
generated code is validated against your architecture **before it lands on disk**, not
|
|
6
|
+
after the PR is red.
|
|
7
|
+
|
|
8
|
+
Everything below uses the same `ark.config.json` as `ark-check` (CI) — one contract, enforced everywhere. Generate it once:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx ark-check --init
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For guided setup with prompts, use:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx ark init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For non-interactive defaults, use:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx ark init --yes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can also generate only the starter gate files for common agent runtimes and CI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx ark-check --install-agent-gates
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The command writes templates for `.mcp.json`, Claude hooks, Cursor MCP/rules,
|
|
33
|
+
GitHub Actions, `AGENTS.md`, a Codex TOML snippet under `docs/`, and (when
|
|
34
|
+
selected) Grok Build project files under `.grok/`. It skips existing files unless
|
|
35
|
+
you pass `--force`, so review and commit only the templates that match your project.
|
|
36
|
+
|
|
37
|
+
If your project uses Codex or Grok, treat MCP registration as part of the default
|
|
38
|
+
setup, not an optional extra. Ark works best when the agent can read `ark://manifest`
|
|
39
|
+
before it writes code; that is the fast path to avoiding architecture drift during
|
|
40
|
+
generation.
|
|
41
|
+
|
|
42
|
+
## Claude Code — hook (recommended, hard block)
|
|
43
|
+
|
|
44
|
+
`ark-mcp --hook` is a one-shot PreToolUse gate: it reads the hook payload from stdin, computes the **post-edit** file content, validates it, and exits `2` (block, violations on stderr) or `0` (allow). The agent sees the violations and self-corrects.
|
|
45
|
+
|
|
46
|
+
Like `ark-check --baseline`, the hook ratchets: an edit is blocked only when it **adds**
|
|
47
|
+
violations relative to the file's current on-disk state, so files with pre-existing
|
|
48
|
+
(baselined) violations stay editable — they just can't get worse. New files block on
|
|
49
|
+
every violation.
|
|
50
|
+
|
|
51
|
+
Add to your project's `.claude/settings.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"hooks": {
|
|
56
|
+
"PreToolUse": [
|
|
57
|
+
{
|
|
58
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
59
|
+
"hooks": [
|
|
60
|
+
{
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "npx ark-mcp --hook --root \"$CLAUDE_PROJECT_DIR\" --config ark.config.json"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That's the whole setup. Try asking the agent to import a persistence adapter from your domain layer:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Ark architecture gate blocked this write to src/domain/order.ts (layer: DomainModel):
|
|
75
|
+
- [FORBIDDEN_PATTERN] Forbidden pattern matched: /from ['"].*\/(infra|adapters|persistence|db)/i (line 1)
|
|
76
|
+
- [FORBIDDEN_IMPORT] Forbidden import target: "../adapters/persistence/pg-order-repository". (line 1)
|
|
77
|
+
Fix the violations and retry. The architecture contract is available as the ark://manifest MCP resource.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Claude Code — SessionStart context injection (know the rules before the first token)
|
|
81
|
+
|
|
82
|
+
The write gate teaches by rejection; the SessionStart hook teaches up front.
|
|
83
|
+
`ark-mcp --session-context` prints a compact contract summary — layers, forbidden
|
|
84
|
+
globals, denied-edge count, baseline state, and the check command — which Claude Code
|
|
85
|
+
injects into the agent's context at session start:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"hooks": {
|
|
90
|
+
"SessionStart": [
|
|
91
|
+
{
|
|
92
|
+
"hooks": [
|
|
93
|
+
{
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": "npx ark-mcp --session-context --root \"$CLAUDE_PROJECT_DIR\" --config ark.config.json"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
What the agent sees:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Ark architecture contract governs this project (ark.config.json is authoritative).
|
|
108
|
+
Layers:
|
|
109
|
+
- DomainModel: src/domain/** — forbidden globals: fetch, process, Date.now, Math.random
|
|
110
|
+
- PersistenceAdapters: src/adapters/persistence/**
|
|
111
|
+
Rules: 10 denied layer edge(s). Full contract: ark://manifest MCP resource.
|
|
112
|
+
Baseline: 3 frozen violation(s) — only NEW violations fail; do not add to them.
|
|
113
|
+
After edits run: npx ark-check --root . --config ark.config.json --strict-config
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The hook belongs in the **project's** `.claude/settings.json` (that's what
|
|
117
|
+
`--install-agent-gates` generates). It is also safe by construction if you prefer it in
|
|
118
|
+
your global settings: without an `ark.config.json` in the project, `--session-context`
|
|
119
|
+
prints nothing and exits 0, so non-Ark projects are untouched.
|
|
120
|
+
|
|
121
|
+
## Claude Code — MCP server (contract discovery + on-demand validation)
|
|
122
|
+
|
|
123
|
+
The MCP server exposes a resource and four tools agents can use proactively:
|
|
124
|
+
|
|
125
|
+
- **`ark://manifest`** (resource) — the machine-readable architecture contract (layers + rules), so the agent can read the architecture before generating code.
|
|
126
|
+
- **`validate_code`** (tool) — validates a snippet against the architecture on demand (the write-path gate).
|
|
127
|
+
- **`ark_place`** (tool) — given a target file path, returns its layer, forbidden globals, and which layers it may / must not import. Call it *before* writing a new file so generated code lands in a governed location.
|
|
128
|
+
- **`ark_check`** (tool) — runs the full architecture check and returns structured violations (applies the baseline automatically when one exists).
|
|
129
|
+
- **`ark_coverage`** (tool) — per-layer file counts, the full unclassified-file list, and layers whose patterns match nothing.
|
|
130
|
+
|
|
131
|
+
Tools appear in the agent's tool list automatically — no skill or doc-reading needed — so the agent can query the contract instead of shelling out and parsing.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
claude mcp add ark -- npx ark-mcp --root . --config ark.config.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
or in `.mcp.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"ark": {
|
|
143
|
+
"type": "stdio",
|
|
144
|
+
"command": "npx",
|
|
145
|
+
"args": ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Use both: the MCP server for discovery, the hook for enforcement.
|
|
152
|
+
|
|
153
|
+
## Cursor
|
|
154
|
+
|
|
155
|
+
Cursor supports MCP servers (`.cursor/mcp.json`):
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"mcpServers": {
|
|
160
|
+
"ark": {
|
|
161
|
+
"command": "npx",
|
|
162
|
+
"args": ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Cursor has no pre-write hook, so the gate is advisory at write time — pair it with a rules file so the agent actually calls it. `.cursor/rules/ark.mdc`:
|
|
169
|
+
|
|
170
|
+
```markdown
|
|
171
|
+
---
|
|
172
|
+
description: Ark architecture contract
|
|
173
|
+
alwaysApply: true
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
Before writing or editing any TypeScript source file, call the `validate_code`
|
|
177
|
+
tool from the `ark` MCP server with the full post-edit file content and its
|
|
178
|
+
path. If it reports violations, fix them before writing. The architecture
|
|
179
|
+
contract is available as the `ark://manifest` resource.
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Your hard backstop in Cursor is CI: `ark-check` fails the PR on anything that slips through.
|
|
183
|
+
|
|
184
|
+
## OpenAI Codex CLI
|
|
185
|
+
|
|
186
|
+
Recommended for Ark projects.
|
|
187
|
+
|
|
188
|
+
`~/.codex/config.toml`:
|
|
189
|
+
|
|
190
|
+
```toml
|
|
191
|
+
[mcp_servers.ark]
|
|
192
|
+
command = "npx"
|
|
193
|
+
args = ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Same model as Cursor: MCP for discovery/validation, `ark-check` in CI as the hard gate.
|
|
197
|
+
For Ark projects, register the MCP server as soon as the repo is adopted so the agent
|
|
198
|
+
has the contract available from the first edit.
|
|
199
|
+
|
|
200
|
+
`ark-check --install-agent-gates --tools codex` auto-merges absolute paths into
|
|
201
|
+
`~/.codex/config.toml` and can install `/ark-*` prompts with `--codex-home`.
|
|
202
|
+
|
|
203
|
+
## Grok Build (xAI)
|
|
204
|
+
|
|
205
|
+
Grok reads project rules from **`AGENTS.md`**, project MCP from **`.grok/config.toml`**
|
|
206
|
+
(and repo-root `.mcp.json`), skills from **`.grok/skills/<name>/SKILL.md`**, and
|
|
207
|
+
hooks from **`.grok/hooks/*.json`**.
|
|
208
|
+
|
|
209
|
+
Install everything Ark needs for Grok:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx ark-check --install-agent-gates --tools grok
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
That writes:
|
|
216
|
+
|
|
217
|
+
| Path | Role |
|
|
218
|
+
|------|------|
|
|
219
|
+
| `.grok/config.toml` | `[mcp_servers.ark]` → `ark-mcp` (relative `--root .`) |
|
|
220
|
+
| `.grok/hooks/ark-write-gate.json` | SessionStart context + PreToolUse write gate |
|
|
221
|
+
| `.grok/skills/ark-*/SKILL.md` | All `/ark-*` skills (slash-invocable) |
|
|
222
|
+
| `AGENTS.md` + `.mcp.json` + CI | Shared with other hosts |
|
|
223
|
+
|
|
224
|
+
Grok also loads Claude/Cursor MCP and skill paths when compat is enabled, so a repo
|
|
225
|
+
already wired for Claude often “just works” in Grok — but the native `.grok/*` layout
|
|
226
|
+
is the supported, commit-friendly path.
|
|
227
|
+
|
|
228
|
+
**Write gate:** Grok’s PreToolUse uses camelCase payloads (`toolName` / `toolInput`)
|
|
229
|
+
and may call `write` / `search_replace`. `ark-mcp --hook` accepts both Claude and Grok
|
|
230
|
+
shapes and returns a Grok-compatible `{ "decision": "deny", "reason": "…" }` on stdout
|
|
231
|
+
when it blocks.
|
|
232
|
+
|
|
233
|
+
**Trust:** the first time you open a project with hooks, run `/hooks-trust` (or
|
|
234
|
+
`grok --trust`) so project hooks and local MCP are allowed.
|
|
235
|
+
|
|
236
|
+
Manual MCP only:
|
|
237
|
+
|
|
238
|
+
```toml
|
|
239
|
+
# .grok/config.toml (or: grok mcp add --scope project ark -- npx ark-mcp --root . --config ark.config.json)
|
|
240
|
+
[mcp_servers.ark]
|
|
241
|
+
command = "npx"
|
|
242
|
+
args = ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Then restart Grok or refresh via `/mcps`. Pair with CI `ark-check` as the hard merge gate.
|
|
246
|
+
|
|
247
|
+
## Instruction-tier agents: Windsurf, Cline, Copilot, Kiro, Roo Code, Continue, Gemini CLI
|
|
248
|
+
|
|
249
|
+
Agents without MCP or hook support still follow the contract through an always-on
|
|
250
|
+
project rule file. `ark-check --install-agent-gates` generates them (auto-detected
|
|
251
|
+
from `.windsurf/`, `.clinerules/`, `.kiro/`, `.roo/`, `.continue/`, `.gemini/`;
|
|
252
|
+
Copilot is explicit-only):
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx ark-check --install-agent-gates --tools windsurf,cline,copilot,kiro,roo,continue,gemini
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Tool | File written |
|
|
259
|
+
|------|--------------|
|
|
260
|
+
| Windsurf | `.windsurf/rules/ark.md` |
|
|
261
|
+
| Cline | `.clinerules/ark.md` |
|
|
262
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
263
|
+
| Kiro | `.kiro/steering/ark.md` |
|
|
264
|
+
| Roo Code | `.roo/rules/ark.md` |
|
|
265
|
+
| Continue | `.continue/rules/ark.md` |
|
|
266
|
+
| Gemini CLI | `GEMINI.md` (its primary context file; also reads `AGENTS.md`) |
|
|
267
|
+
|
|
268
|
+
All of them derive from the same contract as `AGENTS.md` and the Cursor rule, so the
|
|
269
|
+
steps cannot drift. These are advisory (the agent reads rules; nothing blocks the
|
|
270
|
+
write) — keep `ark-check` in CI as the hard gate.
|
|
271
|
+
|
|
272
|
+
## Any other agent runtime with shell hooks
|
|
273
|
+
|
|
274
|
+
If your runtime can run a shell command before file writes and pass the tool payload on stdin (Claude Code or Grok PreToolUse contracts), `ark-mcp --hook` works as-is. The contract:
|
|
275
|
+
|
|
276
|
+
- stdin (Claude): JSON `{ "tool_name": "Write|Edit|MultiEdit", "tool_input": { "file_path": ..., ... } }`
|
|
277
|
+
- stdin (Grok): JSON `{ "toolName": "write|search_replace|…", "toolInput": { "file_path": ..., ... } }` (also accepts Claude names)
|
|
278
|
+
- exit `0` → allow; exit `2` → block, human-readable violations on stderr
|
|
279
|
+
- Grok payloads also get `{ "decision": "deny", "reason": "…" }` on stdout when blocked
|
|
280
|
+
- plumbing problems (no stdin, non-source files, files outside `--root`) never block
|
|
281
|
+
|
|
282
|
+
## ESLint (editor feedback)
|
|
283
|
+
|
|
284
|
+
For in-editor red squiggles on layer violations, add the ESLint plugin. It ships a
|
|
285
|
+
flat-config preset you can spread directly:
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
// eslint.config.js (flat config)
|
|
289
|
+
import ark from 'arkgate/eslint';
|
|
290
|
+
|
|
291
|
+
export default [
|
|
292
|
+
ark.configs.recommended, // ark/no-domain-infra-imports, no-raw-event-publish, require-publish-source
|
|
293
|
+
];
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`recommended` deliberately omits **`ark/no-forbidden-globals`** (domain purity: no
|
|
297
|
+
`fetch`, `Date.now`, `Math.random`, …). That rule is only correct when scoped to your
|
|
298
|
+
domain directories — a global block would flag legitimate infrastructure code. Add it
|
|
299
|
+
as its own block with a `files` glob matching your `DomainModel` layer:
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
import ark from 'arkgate/eslint';
|
|
303
|
+
|
|
304
|
+
export default [
|
|
305
|
+
ark.configs.recommended,
|
|
306
|
+
{
|
|
307
|
+
files: ['packages/*/domain/**', 'src/**/domain/**'], // your DomainModel layer paths
|
|
308
|
+
plugins: { ark },
|
|
309
|
+
rules: {
|
|
310
|
+
'ark/no-forbidden-globals': [
|
|
311
|
+
'error',
|
|
312
|
+
{ globals: ['fetch', 'process', 'Date.now', 'Math.random'] }, // match ark.config.json
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Rule ids are `ark/<kebab-name>`. Keep the `globals` list in sync with the
|
|
320
|
+
`forbiddenGlobals` on your `DomainModel` layer in `ark.config.json` so the editor and
|
|
321
|
+
`ark-check` agree. All four rules are exported individually too (`ark.rules`) if you
|
|
322
|
+
prefer to wire them by hand.
|
|
323
|
+
|
|
324
|
+
## CI backstop
|
|
325
|
+
|
|
326
|
+
Whatever the agent side does, gate the merge:
|
|
327
|
+
|
|
328
|
+
```yaml
|
|
329
|
+
- run: npx ark-check --root . --config ark.config.json --strict-config
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Adopting Ark on an existing codebase with violations? Freeze them once and ratchet down:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npx ark-check --update-baseline # writes .ark-baseline.json — commit it
|
|
336
|
+
npx ark-check --baseline # only NEW violations fail
|
|
337
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["src"],
|
|
3
|
+
"layers": [
|
|
4
|
+
{
|
|
5
|
+
"name": "DomainModel",
|
|
6
|
+
"patterns": ["src/domain/**"],
|
|
7
|
+
"intentPrefixes": ["Domain."]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"name": "ApplicationOrchestration",
|
|
11
|
+
"patterns": ["src/application/**"],
|
|
12
|
+
"intentPrefixes": ["Application."]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "PersistenceAdapters",
|
|
16
|
+
"patterns": ["src/adapters/persistence/**"],
|
|
17
|
+
"intentPrefixes": ["Adapter.Persistence."]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "IntegrationAdapters",
|
|
21
|
+
"patterns": ["src/adapters/integration/**"],
|
|
22
|
+
"intentPrefixes": ["Adapter.Integration."]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "WorkflowSagaEngine",
|
|
26
|
+
"patterns": ["src/workflows/**"],
|
|
27
|
+
"intentPrefixes": ["Workflow."]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "BackgroundJobsScheduling",
|
|
31
|
+
"patterns": ["src/jobs/**"],
|
|
32
|
+
"intentPrefixes": ["Job."]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "PresentationAdapters",
|
|
36
|
+
"patterns": ["src/presentation/**"],
|
|
37
|
+
"intentPrefixes": ["Presentation."]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "ReportingReadModels",
|
|
41
|
+
"patterns": ["src/reporting/**"],
|
|
42
|
+
"intentPrefixes": ["Reporting."]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "ExtensibilityMetadata",
|
|
46
|
+
"patterns": ["src/metadata/**"],
|
|
47
|
+
"intentPrefixes": ["Metadata."]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "SecurityAuditObservability",
|
|
51
|
+
"patterns": ["src/security/**", "src/audit/**", "src/observability/**"],
|
|
52
|
+
"intentPrefixes": ["Security.", "Audit.", "Observability."]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "Kernel",
|
|
56
|
+
"patterns": ["src/kernel/**"],
|
|
57
|
+
"intentPrefixes": ["Kernel."]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"rules": [
|
|
61
|
+
{
|
|
62
|
+
"from": "DomainModel",
|
|
63
|
+
"to": "PersistenceAdapters",
|
|
64
|
+
"allowed": false
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"from": "DomainModel",
|
|
68
|
+
"to": "IntegrationAdapters",
|
|
69
|
+
"allowed": false
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"from": "DomainModel",
|
|
73
|
+
"to": "PresentationAdapters",
|
|
74
|
+
"allowed": false
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"from": "PresentationAdapters",
|
|
78
|
+
"to": "PersistenceAdapters",
|
|
79
|
+
"allowed": false
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"from": "ReportingReadModels",
|
|
83
|
+
"to": "PersistenceAdapters",
|
|
84
|
+
"allowed": false
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="330" viewBox="0 0 760 330" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13">
|
|
2
|
+
<defs>
|
|
3
|
+
<clipPath id="window"><rect x="0" y="0" width="760" height="330" rx="10"/></clipPath>
|
|
4
|
+
</defs>
|
|
5
|
+
<g clip-path="url(#window)">
|
|
6
|
+
<rect width="760" height="330" fill="#0d1117"/>
|
|
7
|
+
<rect width="760" height="34" fill="#161b22"/>
|
|
8
|
+
<circle cx="20" cy="17" r="6" fill="#ff5f57"/>
|
|
9
|
+
<circle cx="40" cy="17" r="6" fill="#febc2e"/>
|
|
10
|
+
<circle cx="60" cy="17" r="6" fill="#28c840"/>
|
|
11
|
+
<text x="380" y="21" fill="#8b949e" text-anchor="middle" font-size="12">claude — my-hexagonal-app</text>
|
|
12
|
+
|
|
13
|
+
<text x="24" y="64" fill="#c9d1d9">> add a findById method to Order that reads straight from postgres</text>
|
|
14
|
+
|
|
15
|
+
<text x="24" y="96" fill="#8b949e">● Edit(src/domain/order.ts)</text>
|
|
16
|
+
<text x="40" y="118" fill="#6e7681">import { PgOrderRepository } from '../adapters/persistence/pg-order-repository';</text>
|
|
17
|
+
|
|
18
|
+
<text x="24" y="152" fill="#f85149" font-weight="bold">✗ Ark architecture gate blocked this write to src/domain/order.ts (layer: DomainModel):</text>
|
|
19
|
+
<text x="40" y="174" fill="#f85149">- [FORBIDDEN_IMPORT] Forbidden import target: "../adapters/persistence/pg-order-repository".</text>
|
|
20
|
+
<text x="24" y="196" fill="#8b949e">Fix the violations and retry. The architecture contract is available as the</text>
|
|
21
|
+
<text x="24" y="214" fill="#8b949e">ark://manifest MCP resource.</text>
|
|
22
|
+
|
|
23
|
+
<text x="24" y="248" fill="#c9d1d9">● The domain layer can't import persistence adapters. I'll define the port in the</text>
|
|
24
|
+
<text x="24" y="266" fill="#c9d1d9">domain instead and implement it in src/adapters/persistence/.</text>
|
|
25
|
+
|
|
26
|
+
<text x="24" y="300" fill="#28c840">● Edit(src/domain/ports/order-repository.ts) ✔ allowed</text>
|
|
27
|
+
</g>
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Brownfield burn-down playbook
|
|
2
|
+
|
|
3
|
+
Adopting Ark on a large, pre-existing codebase is a different job from a greenfield one: the
|
|
4
|
+
goal is not "make the check green" — it's to get the contract to reflect the real
|
|
5
|
+
architecture, govern most of the tree, and freeze only genuine debt, then burn it down in
|
|
6
|
+
order. This playbook is the sequence that keeps you honest (and is what `/ark-adopt` runs).
|
|
7
|
+
|
|
8
|
+
The rule underneath all of it: **`ark.config.json` is authoritative.** A green check that
|
|
9
|
+
governs 40% of the tree, or a baseline full of false positives, is worse than no gate — it
|
|
10
|
+
looks safe. Report the truth, then fix it.
|
|
11
|
+
|
|
12
|
+
## 1. Config — reflect what exists, propose the rest
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
ark-check --init # detects layer directories; proposes a layer for each ungoverned one
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`--init` writes layers for the directories it recognizes and **proposes a canonical layer for
|
|
19
|
+
every ungoverned directory** (harvested from the 11-layer profile + presets; unrecognized
|
|
20
|
+
ones are flagged for you to classify, never guessed). Keep an existing config; don't
|
|
21
|
+
regenerate it unasked.
|
|
22
|
+
|
|
23
|
+
## 2. Diagnose before you freeze
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ark-check --root . --config ark.config.json --json # read `summary`
|
|
27
|
+
ark-check --doctor # or the consolidated health view
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Read the violation `summary`. The decisive signal is **concentration**: when most violations
|
|
31
|
+
are a single layer edge, the *contract* is almost always wrong, not the code. The classic
|
|
32
|
+
case is every route "violating" app→kernel because the framework's own entrypoint
|
|
33
|
+
(`defineRoute`, a DI container, etc.) is the sanctioned way in — that's hundreds of false
|
|
34
|
+
positives, not debt. `--update-baseline` will refuse a lopsided freeze for exactly this
|
|
35
|
+
reason. Fix the contract first (step 3).
|
|
36
|
+
|
|
37
|
+
## 3. Make the contract real (via `/ark-contract`)
|
|
38
|
+
|
|
39
|
+
- **Classify the ungoverned tree.** `ark-check --coverage` leads with `Governed: N%` and
|
|
40
|
+
proposes a layer per ungoverned directory. Add the recognized ones; decide the layer for the
|
|
41
|
+
flagged ones. Get `governed` high before trusting any check.
|
|
42
|
+
- **Protect the border around a framework, not its internals.** If the concentrated edge
|
|
43
|
+
points into a DI/kernel framework (dcouplr, NestJS, a custom kernel), split the target layer
|
|
44
|
+
into a **public surface** (the entrypoints app code may import — e.g. `kernel/app/**`,
|
|
45
|
+
`kernel/events.ts`) and **internals** (denied). Overlapping globs resolve most-specific-first,
|
|
46
|
+
so the surface wins regardless of layer order. Where app code reaches an internal entrypoint
|
|
47
|
+
it legitimately needs, add a re-export **barrel** in the surface layer and repoint the
|
|
48
|
+
imports to it — behavior-preserving, and the imports are now legal.
|
|
49
|
+
|
|
50
|
+
Re-run the check; the remainder should now be genuine debt.
|
|
51
|
+
|
|
52
|
+
## 4. Freeze only the genuine debt
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ark-check --update-baseline # writes .ark-baseline.json — commit it
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If Ark still refuses (a single edge dominates), the contract is still wrong — go back to
|
|
59
|
+
step 3; don't `--force` past it. From now `ark-check --baseline` fails only on NEW
|
|
60
|
+
violations — the ratchet only moves toward zero.
|
|
61
|
+
|
|
62
|
+
## 5. Burn down, in order
|
|
63
|
+
|
|
64
|
+
`summary.edges` is the burn-down order. Two patterns cover most of it — and Ark tells you
|
|
65
|
+
which is which via the `typeOnly` tag (value = real coupling, fix first; type-only = placement):
|
|
66
|
+
|
|
67
|
+
- **Type-only inversion** (a lower layer `import type`s a type that lives in an upper layer):
|
|
68
|
+
move the type down to the layer that owns it and re-export it from the original module for
|
|
69
|
+
back-compat. Cosmetic at runtime, `tsc`-verifiable, safe to sweep. (Not mechanical if the
|
|
70
|
+
type extends a persistence/ORM row — that needs a domain-owned type/port — or if the source
|
|
71
|
+
file mixes types with logic — split first.)
|
|
72
|
+
- **Raw infrastructure access** (a route/handler running SQL or importing the DB directly):
|
|
73
|
+
relocate the data-access **verbatim** into a repository/adapter method the route calls. Same
|
|
74
|
+
query bytes = same behavior; do NOT rewrite the query. This edits the data layer — if your
|
|
75
|
+
repo reserves that to core maintainers, migrate one route as a demonstrated pattern and hand
|
|
76
|
+
the bulk over; a route with interleaved transactions isn't a pure relocation, so flag it.
|
|
77
|
+
|
|
78
|
+
`/ark-fix` resolves each cluster at the root cause; fixing a frozen violation shrinks the
|
|
79
|
+
baseline permanently. Re-freeze lower with `--update-baseline` as you go.
|
|
80
|
+
|
|
81
|
+
## What Ark does NOT do here
|
|
82
|
+
|
|
83
|
+
Ark reorganizes and governs code — it never touches your data model. Migrating raw SQL to a
|
|
84
|
+
repository moves the same query to another file; the schema, migrations, and the database are
|
|
85
|
+
untouched. And the burn-down itself is the team's work (or a codemod, or an agent loop) — Ark
|
|
86
|
+
diagnoses, orders it, and gives you the pattern; it doesn't auto-run hundreds of edits against
|
|
87
|
+
your restricted data layer.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Demo: write-gate self-correction
|
|
2
|
+
|
|
3
|
+
Reproduce how an agent sees a layer violation, gets a plain-language fix hint, and
|
|
4
|
+
corrects the architecture instead of weakening the contract.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- Ark installed in the repo (`ark-check`, agent gates optional for this demo)
|
|
9
|
+
- [examples/hexagonal-order-api](../../examples/hexagonal-order-api/) cloned with dependencies
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
### 1. Confirm the project is green
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd examples/hexagonal-order-api
|
|
17
|
+
npm install
|
|
18
|
+
npm run check
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Expected:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
✔ Ark check passed.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Introduce a deliberate domain → persistence import
|
|
28
|
+
|
|
29
|
+
Add to the top of `src/domain/order.ts`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { createInMemoryOrderRepository } from '../adapters/persistence/in-memory-order-repository.js';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Run the CI gate (same stimulus as the write-gate hook)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run check
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Expected violation (abbreviated):
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
✖ LAYER_IMPORT_VIOLATION src/domain/order.ts
|
|
45
|
+
DomainModel → PersistenceAdapters
|
|
46
|
+
DomainModel must not import PersistenceAdapters.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With `--json`, the same violation includes `fixClass: port-inversion` and an
|
|
50
|
+
`enthusiastHint` describing the port pattern.
|
|
51
|
+
|
|
52
|
+
### 4. Self-correct (architecture fix, not a config edit)
|
|
53
|
+
|
|
54
|
+
Revert the bad import. Define or use the existing `OrderRepository` port in domain and
|
|
55
|
+
keep the in-memory implementation in `src/adapters/persistence/`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Expected:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
✔ Ark check passed.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What this proves
|
|
68
|
+
|
|
69
|
+
- The gate blocks the common enthusiast mistake (database code in domain).
|
|
70
|
+
- The error message points at the **layer edge**, not a generic lint rule.
|
|
71
|
+
- The fix is a file move or port inversion — not editing `ark.config.json`.
|
|
72
|
+
|
|
73
|
+
See also: [Break it on purpose](../../examples/hexagonal-order-api/README.md#break-it-on-purpose)
|
|
74
|
+
for two more exercises.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Demo: brownfield baseline adoption
|
|
2
|
+
|
|
3
|
+
Walk through honest adoption on an existing repo: diagnose, classify, freeze real debt,
|
|
4
|
+
burn down in order. This demo uses the eval fixture `enthusiast-wrong-layer` as a
|
|
5
|
+
stand-in brownfield tree.
|
|
6
|
+
|
|
7
|
+
Full playbook: [docs/brownfield-adoption.md](../brownfield-adoption.md).
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Ark built or installed from this repository
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
### 1. Copy the brownfield fixture to a temp directory
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
TMP=$(mktemp -d)
|
|
19
|
+
cp -R eval/cases/enthusiast-wrong-layer/* "$TMP/"
|
|
20
|
+
cd "$TMP"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Diagnose — read the violation summary
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node /path/to/ark/bin/ark-check.mjs --root . --config ark.config.json --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Expected: `LAYER_IMPORT_VIOLATION` from domain importing `src/infra/db.ts`.
|
|
30
|
+
|
|
31
|
+
### 3. Classify — coverage and doctor
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node /path/to/ark/bin/ark-check.mjs --root . --doctor
|
|
35
|
+
node /path/to/ark/bin/ark-check.mjs --root . --coverage
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Note `governed.percent` and any ungoverned directories. Do **not** regenerate
|
|
39
|
+
`ark.config.json` unasked on a real brownfield repo.
|
|
40
|
+
|
|
41
|
+
### 4. Fix architecture (not the gate)
|
|
42
|
+
|
|
43
|
+
Move persistence behind a port in domain; relocate `db.ts` under
|
|
44
|
+
`src/adapters/persistence/` (or invert via a repository interface). Re-run:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node /path/to/ark/bin/ark-check.mjs --root . --config ark.config.json --strict-config
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Expected after a real fix:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
✔ Ark check passed.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 5. Baseline only genuine remaining debt
|
|
57
|
+
|
|
58
|
+
If violations are widespread but **concentrated on one false edge**, fix the contract
|
|
59
|
+
first (see playbook §3). When the remainder is real legacy debt:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
node /path/to/ark/bin/ark-check.mjs --root . --update-baseline
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
New files must still pass without suppression.
|
|
66
|
+
|
|
67
|
+
## What this proves
|
|
68
|
+
|
|
69
|
+
- Brownfield adoption starts with truthful diagnosis, not a green check at any cost.
|
|
70
|
+
- `/ark-adopt` and `/ark-contract` align messy trees to the canonical profile.
|
|
71
|
+
- Baselines ratchet down; they do not replace architecture fixes.
|