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.
Files changed (64) hide show
  1. package/CHANGELOG.md +1249 -0
  2. package/LICENSE +21 -0
  3. package/README.md +218 -0
  4. package/SECURITY.md +39 -0
  5. package/bin/ark-check.mjs +5204 -0
  6. package/bin/ark-mcp.mjs +898 -0
  7. package/bin/ark-shared.mjs +1520 -0
  8. package/bin/ark.mjs +491 -0
  9. package/dist/eslint/index.cjs +222 -0
  10. package/dist/eslint/index.cjs.map +1 -0
  11. package/dist/eslint/index.d.cts +42 -0
  12. package/dist/eslint/index.d.ts +40 -0
  13. package/dist/eslint/index.js +193 -0
  14. package/dist/eslint/index.js.map +1 -0
  15. package/dist/index.cjs +3080 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +577 -0
  18. package/dist/index.d.ts +577 -0
  19. package/dist/index.js +2998 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/nestjs/index.cjs +2332 -0
  22. package/dist/nestjs/index.cjs.map +1 -0
  23. package/dist/nestjs/index.d.cts +22 -0
  24. package/dist/nestjs/index.d.ts +22 -0
  25. package/dist/nestjs/index.js +2308 -0
  26. package/dist/nestjs/index.js.map +1 -0
  27. package/dist/types-DpdVN7Lm.d.cts +1023 -0
  28. package/dist/types-DpdVN7Lm.d.ts +1023 -0
  29. package/docs/agent-guide.md +490 -0
  30. package/docs/ai-gates.md +337 -0
  31. package/docs/ark-check-example.json +87 -0
  32. package/docs/assets/ark-write-gate.svg +28 -0
  33. package/docs/brownfield-adoption.md +87 -0
  34. package/docs/demos/01-write-gate-self-correction.md +74 -0
  35. package/docs/demos/02-brownfield-baseline-adoption.md +71 -0
  36. package/docs/demos/03-copilot-autopilot.md +83 -0
  37. package/docs/enthusiast/README.md +62 -0
  38. package/docs/enthusiast/explanation-application-shape.md +29 -0
  39. package/docs/enthusiast/how-to-agent-gates.md +36 -0
  40. package/docs/enthusiast/how-to-gallery-starter.md +27 -0
  41. package/docs/enthusiast/how-to-pick-shape.md +45 -0
  42. package/docs/enthusiast/how-to-policy-pack.md +37 -0
  43. package/docs/enthusiast/reference-archetypes.md +36 -0
  44. package/docs/enthusiast/reference-commands.md +50 -0
  45. package/docs/enthusiast/tutorial-first-project.md +86 -0
  46. package/docs/production-hardening.md +59 -0
  47. package/package.json +125 -0
  48. package/server.json +39 -0
  49. package/templates/architecture-playbook.json +339 -0
  50. package/templates/policy-packs/enthusiast-feature-sliced.json +20 -0
  51. package/templates/policy-packs/enthusiast-hexagonal.json +18 -0
  52. package/templates/policy-packs/enthusiast-layered.json +18 -0
  53. package/templates/policy-packs/enthusiast-monorepo.json +18 -0
  54. package/templates/skills/ark-adopt.md +103 -0
  55. package/templates/skills/ark-architect.md +90 -0
  56. package/templates/skills/ark-autopilot.md +95 -0
  57. package/templates/skills/ark-contract.md +98 -0
  58. package/templates/skills/ark-coverage.md +96 -0
  59. package/templates/skills/ark-explain.md +78 -0
  60. package/templates/skills/ark-fix.md +96 -0
  61. package/templates/skills/ark-loop.md +69 -0
  62. package/templates/skills/ark-place.md +68 -0
  63. package/templates/skills/ark-runtime.md +62 -0
  64. package/templates/skills/ark-upgrade.md +109 -0
@@ -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">&gt; 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.