ark-runtime-kernel 1.0.0 → 1.2.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/README.md +133 -219
- package/bin/ark-check.mjs +316 -20
- package/bin/ark-mcp.mjs +0 -0
- package/bin/ark-postinstall.mjs +12 -0
- package/bin/ark.mjs +129 -0
- package/dist/index.cjs +26 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -958
- package/dist/index.d.ts +5 -958
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +2301 -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 +2298 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/types-7K_KQCgS.d.cts +991 -0
- package/dist/types-7K_KQCgS.d.ts +991 -0
- package/docs/agent-guide.md +344 -0
- package/docs/ai-gates.md +169 -0
- package/docs/ark-check-example.json +87 -0
- package/docs/assets/ark-write-gate.svg +28 -0
- package/docs/blog/how-i-stopped-claude-from-breaking-my-architecture.md +85 -0
- package/docs/production-hardening.md +59 -0
- package/package.json +46 -8
- package/server.json +31 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Ark — Agent Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide describes how AI agents and codegen tools can safely interact with Ark.
|
|
4
|
+
|
|
5
|
+
## Contract Discovery
|
|
6
|
+
|
|
7
|
+
Prefer `createStrictArkKernel()` for strict projects. It wires the registry, graph,
|
|
8
|
+
policies, event bus, audit trail, event contracts, outbox, observability,
|
|
9
|
+
projections, metadata, workflow engine, and 11-layer architecture profile:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
createStrictArkKernel,
|
|
14
|
+
} from 'ark-runtime-kernel';
|
|
15
|
+
|
|
16
|
+
const ark = createStrictArkKernel();
|
|
17
|
+
// ... define intents, event contracts, metadata, projections, and workflows through ark.*
|
|
18
|
+
|
|
19
|
+
const contract = ark.manifest().toJSON();
|
|
20
|
+
// contract.intents, policies, entities, graph, architecture, eventContracts,
|
|
21
|
+
// contract.observability, projections
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Agents should read `contract` and `ark.observability.report()` before generating or modifying code.
|
|
25
|
+
|
|
26
|
+
## Naming Conventions
|
|
27
|
+
|
|
28
|
+
| Prefix | Layer | Example |
|
|
29
|
+
|--------|-------|---------|
|
|
30
|
+
| `Domain.*` | Domain events & entities | `Domain.Order.OrderPlaced` |
|
|
31
|
+
| `Application.*` | Use cases / orchestration | `Application.PlaceOrder` |
|
|
32
|
+
| `Adapter.Persistence.*` | Persistence adapters | `Adapter.Persistence.OrderRepo` |
|
|
33
|
+
| `Adapter.Integration.*` | External integrations | `Adapter.Integration.PaymentGateway.Charge` |
|
|
34
|
+
| `Workflow.*` | Sagas / long-running processes | `Workflow.OrderFulfillment` |
|
|
35
|
+
| `Job.*` | Background jobs / scheduling | `Job.InventoryRebuild` |
|
|
36
|
+
| `Presentation.*` | UI/API adapters | `Presentation.Api.PlaceOrder` |
|
|
37
|
+
| `Reporting.*` | Read models / projections | `Reporting.OrderSummary` |
|
|
38
|
+
| `Metadata.*` | Metadata and extension contracts | `Metadata.OrderSchema` |
|
|
39
|
+
| `Security.*`, `Audit.*`, `Observability.*` | Cross-cutting concerns | `Audit.OrderHistory` |
|
|
40
|
+
| `Kernel.*` | Ark-owned governance signals | `Kernel.PolicyViolation` |
|
|
41
|
+
|
|
42
|
+
Declare relationships at definition time:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
registry.define('Application.PlaceOrder', {
|
|
46
|
+
dependsOn: ['Domain.Order.OrderPlaced'],
|
|
47
|
+
produces: ['Domain.Order.OrderPlaced'],
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Strict kernels also enforce the **observed** producer→event layer flow at publish time
|
|
52
|
+
(`enforceObservedLayerFlow: 'hard'` by default). If a published event's real source and
|
|
53
|
+
intent cross a forbidden layer boundary — e.g. a `Adapter.Persistence.*` source producing
|
|
54
|
+
a `Domain.*` event — the publish throws `ObservedLayerFlowViolationError` before the event
|
|
55
|
+
reaches history, outbox, or subscribers. Use `'soft'` to record `layer.observedViolation`
|
|
56
|
+
trace/audit records without blocking, or `'off'` to disable. Agents should name the event's
|
|
57
|
+
`source` honestly: it is checked against the layer matrix, not just the intent name.
|
|
58
|
+
|
|
59
|
+
Strict kernels also require published events to have a registered source intent
|
|
60
|
+
and a matching event contract:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const OrderPlaced = registry.define<
|
|
64
|
+
'Domain.Order.OrderPlaced',
|
|
65
|
+
{ orderId: string; amount: number }
|
|
66
|
+
>('Domain.Order.OrderPlaced');
|
|
67
|
+
|
|
68
|
+
registry.define('Application.PlaceOrder', {
|
|
69
|
+
produces: ['Domain.Order.OrderPlaced'],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
ark.eventContracts.register({
|
|
73
|
+
intent: 'Domain.Order.OrderPlaced',
|
|
74
|
+
version: '1',
|
|
75
|
+
allowAdditionalFields: false,
|
|
76
|
+
schema: {
|
|
77
|
+
orderId: { type: 'string', required: true },
|
|
78
|
+
amount: { type: 'number', required: true },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const publisher = ark.publisher('Application.PlaceOrder');
|
|
83
|
+
|
|
84
|
+
await publisher.publish(OrderPlaced, { orderId: 'o1', amount: 99 }, {
|
|
85
|
+
eventVersion: '1',
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Agents should prefer `ark.publisher(sourceIntent).publish(...)` over direct
|
|
90
|
+
`eventBus.publish(...)`. Source-bound publishers stamp `metadata.source` internally and
|
|
91
|
+
reject attempts to override it with a different source.
|
|
92
|
+
|
|
93
|
+
Interceptors may enrich event payloads, but they must remain add-only:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
ark.eventBus.registerInterceptor(OrderPlaced, ({ intercept }) => {
|
|
97
|
+
intercept({ auditTag: 'checkout' });
|
|
98
|
+
}, 'audit-tag');
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If an interceptor overwrites an existing field or violates the registered event
|
|
102
|
+
contract, Ark records `interceptor.error` and keeps delivering the original event.
|
|
103
|
+
|
|
104
|
+
## Code Generation Validation
|
|
105
|
+
|
|
106
|
+
Use `createAICodeGate()` before merging agent-generated source snippets:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import * as ts from 'typescript';
|
|
110
|
+
|
|
111
|
+
const gate = createAICodeGate({
|
|
112
|
+
intents: registry.list(),
|
|
113
|
+
enforceIntentAllowlist: true,
|
|
114
|
+
architectureProfile: elevenLayerProfile,
|
|
115
|
+
typescript: ts,
|
|
116
|
+
extensions: [/* optional external AST analyzers implementing AIGateExtension */],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const result = gate.validate(generatedSource, {
|
|
120
|
+
filePath: 'src/domain/order.ts',
|
|
121
|
+
agentId: 'agent-1',
|
|
122
|
+
layer: 'DomainModel',
|
|
123
|
+
});
|
|
124
|
+
if (!result.valid) {
|
|
125
|
+
for (const v of result.violations) {
|
|
126
|
+
console.log(v.code, v.message, v.suggestion);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Passing the `typescript` module enables built-in AST checks for raw publish calls, missing
|
|
132
|
+
`metadata.source`, and source-layer mismatches. `ark-mcp` enables these checks
|
|
133
|
+
automatically when TypeScript is available.
|
|
134
|
+
|
|
135
|
+
Violation codes (from `createAICodeGate`): `RAW_EVENT_PUBLISH`, `PUBLISH_MISSING_SOURCE`, `PUBLISH_SOURCE_LAYER_MISMATCH`, `FORBIDDEN_PATTERN`, `FORBIDDEN_SUBSTRING`, `FORBIDDEN_IMPORT`, `POLICY_VIOLATION`, `UNKNOWN_INTENT`, `LAYER_REFERENCE_VIOLATION`, `EXTENSION_ERROR`, `AST_ANALYZER_ERROR`.
|
|
136
|
+
|
|
137
|
+
Use `ark-check` in CI for repository-level checks that need real file paths:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx ark-check --root . --config ark.config.json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Agents can generate a config from the project's actual directory layout instead of inventing layer mappings:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npx ark-check --init
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or print the full 11-layer template to adapt manually:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx ark-check --print-config eleven-layer
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Example config:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"include": ["src"],
|
|
160
|
+
"layers": [
|
|
161
|
+
{
|
|
162
|
+
"name": "DomainModel",
|
|
163
|
+
"patterns": ["src/domain/**"],
|
|
164
|
+
"intentPrefixes": ["Domain."]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"name": "PersistenceAdapters",
|
|
168
|
+
"patterns": ["src/adapters/persistence/**"],
|
|
169
|
+
"intentPrefixes": ["Adapter.Persistence."]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "ApplicationOrchestration",
|
|
173
|
+
"patterns": ["src/application/**"],
|
|
174
|
+
"intentPrefixes": ["Application."]
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"rules": [
|
|
178
|
+
{
|
|
179
|
+
"from": "DomainModel",
|
|
180
|
+
"to": "PersistenceAdapters",
|
|
181
|
+
"allowed": false
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`ark-check` resolves imports through the TypeScript module resolver against your
|
|
188
|
+
`tsconfig.json` — relative, path-alias (e.g. `@infra/db`), package imports, dynamic
|
|
189
|
+
`import()`, and `require()` — plus string intent references. It also flags raw
|
|
190
|
+
`publish()` calls, publish calls without `metadata.source`, and source intent literals
|
|
191
|
+
whose resolved layer differs from the publishing file layer. Pass `--tsconfig <path>` to point at a specific config
|
|
192
|
+
(otherwise the nearest `tsconfig.json` from `--root` is used). It resolves modules the way
|
|
193
|
+
your build does, but is intentionally not yet a full type-graph analyzer (cross-layer
|
|
194
|
+
type-only references beyond the import specifier are out of scope).
|
|
195
|
+
|
|
196
|
+
`ark-check --json` also reports `warnings` for incomplete governance coverage: missing
|
|
197
|
+
layers, unclassified included files, unmatched layer patterns, duplicate layers, and rules
|
|
198
|
+
that reference unknown layers. These are advisory by default. Use `--strict-config` once a
|
|
199
|
+
project is ready to fail CI on coverage gaps.
|
|
200
|
+
|
|
201
|
+
Use the optional ESLint plugin for fast local feedback:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
import ark from 'ark-runtime-kernel/eslint';
|
|
205
|
+
|
|
206
|
+
export default [
|
|
207
|
+
ark.configs.recommended,
|
|
208
|
+
];
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Rules: `ark/no-domain-infra-imports`, `ark/no-raw-event-publish`, and
|
|
212
|
+
`ark/require-publish-source`.
|
|
213
|
+
|
|
214
|
+
## Runtime Observability
|
|
215
|
+
|
|
216
|
+
The event bus exposes a standard trace format:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const bus = createEventBus({
|
|
220
|
+
maxHistorySize: 1000,
|
|
221
|
+
auditTrail,
|
|
222
|
+
traceSinks: [(record) => otelBridge(record)],
|
|
223
|
+
onSoftViolation: (result, event) => { /* advisory policies */ },
|
|
224
|
+
onHandlerError: (err, event, intent) => { /* subscriber failures */ },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await bus.publish(intent, payload);
|
|
228
|
+
const trace = bus.getTrace();
|
|
229
|
+
// trace[].type includes 'event.published', 'event.rawPublish', 'event.intercepted',
|
|
230
|
+
// 'interceptor.error', 'policy.hardViolation', 'policy.softViolation', 'handler.error'
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Native audit records are available through `auditTrail.query()`. Projection
|
|
234
|
+
state and checkpoints are available through `ProjectionRegistry`.
|
|
235
|
+
|
|
236
|
+
`ark.observability.report()` compares declared productions with observed runtime
|
|
237
|
+
flows. Use `observedButUndeclared` as a high-signal review queue for hidden coupling.
|
|
238
|
+
|
|
239
|
+
For tests, use `createArkTestHarness(ark)` to inspect events, traces, audit,
|
|
240
|
+
outbox, and observability snapshots without reaching into private internals.
|
|
241
|
+
|
|
242
|
+
## Extension Points (External Layers)
|
|
243
|
+
|
|
244
|
+
Implement these interfaces in **external** packages — not inside the Ark core:
|
|
245
|
+
|
|
246
|
+
| Interface | Purpose |
|
|
247
|
+
|-----------|---------|
|
|
248
|
+
| `AIGateExtension` | Plug in AST/semantic analyzers for codegen validation |
|
|
249
|
+
| `Policy` | Custom architectural rules via `definePolicy()` |
|
|
250
|
+
| `LayerFlowRule` | Layer isolation via `defineLayerPolicy()` |
|
|
251
|
+
| `WorkflowStore` | Persist workflow snapshots outside memory |
|
|
252
|
+
| `ReadModelStore` | Persist projection/read-model state outside memory |
|
|
253
|
+
| `AuditStore` | Persist audit records outside memory |
|
|
254
|
+
| `OutboxStore` | Persist event outbox records outside memory |
|
|
255
|
+
| `EventInterceptor` | Add-only event enrichment before delivery |
|
|
256
|
+
|
|
257
|
+
## Ports and Adapters
|
|
258
|
+
|
|
259
|
+
When generating adapter code, prefer ports with explicit ownership and allowlists:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const PaymentGateway = definePort<PaymentGatewayPort>('PaymentGateway', {
|
|
263
|
+
ownerLayer: 'ApplicationOrchestration',
|
|
264
|
+
intent: 'Application.Port.PaymentGateway',
|
|
265
|
+
allowedAdapters: ['Adapter.Integration.StripePaymentGateway'],
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
createAdapter(PaymentGateway, stripeAdapter, {
|
|
269
|
+
name: 'Adapter.Integration.StripePaymentGateway',
|
|
270
|
+
layer: 'IntegrationAdapters',
|
|
271
|
+
requiredKeys: ['charge'],
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`createAdapter` rejects adapter names/intents not listed in `allowedAdapters`; use
|
|
276
|
+
`checkAdapterGovernance(adapter)` when a tool needs a non-throwing result.
|
|
277
|
+
|
|
278
|
+
Preset: `elevenLayerProfile` plus `defineArchitectureProfilePolicy()` forbids invalid declared dependencies across the 11-layer profile. `architecturalPolicies.cleanArchitectureMatrix()` remains available for the older four-prefix model.
|
|
279
|
+
|
|
280
|
+
## Write-Path Gate (MCP)
|
|
281
|
+
|
|
282
|
+
The strongest place to constrain an AI agent is the moment it writes a file, not after.
|
|
283
|
+
`ark-mcp` exposes Ark over MCP (zero dependencies, JSON-RPC over stdio) so a host can gate
|
|
284
|
+
the write path:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npx ark-mcp --root . --config ark.config.json [--manifest ark.manifest.json]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- **Resource `ark://manifest`** — contract discovery. Serve your exported
|
|
291
|
+
`ark.manifest().toJSON()` via `--manifest`, or omit it to get the 11-layer profile
|
|
292
|
+
(layers + rules) as the default contract.
|
|
293
|
+
- **Tool `validate_code`** — args `{ source, layer?, filePath? }`. Runs `createAICodeGate`
|
|
294
|
+
against the profile and (when a manifest is provided) the registered intent allowlist.
|
|
295
|
+
Returns `{ valid, violations, layer }`; `isError` is `true` when invalid. If `layer` is
|
|
296
|
+
omitted it is inferred from `filePath` via the config's layer patterns.
|
|
297
|
+
|
|
298
|
+
For hook-based enforcement, `ark-mcp --hook` runs one-shot: it reads a PreToolUse payload
|
|
299
|
+
from stdin, validates the post-edit file content, and exits `2` with violations on stderr
|
|
300
|
+
to block the write (`0` to allow). Working Claude Code configuration
|
|
301
|
+
(`.claude/settings.json`):
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"hooks": {
|
|
306
|
+
"PreToolUse": [
|
|
307
|
+
{
|
|
308
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
309
|
+
"hooks": [
|
|
310
|
+
{
|
|
311
|
+
"type": "command",
|
|
312
|
+
"command": "npx ark-mcp --hook --root \"$CLAUDE_PROJECT_DIR\""
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Register the server itself in `.mcp.json` so the agent can read `ark://manifest` and call
|
|
322
|
+
`validate_code` on demand:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"mcpServers": {
|
|
327
|
+
"ark": { "command": "npx", "args": ["ark-mcp", "--root", ".", "--config", "ark.config.json"] }
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
This makes the manifest + AI gate an enforced checkpoint rather than a library the agent
|
|
333
|
+
must remember to call.
|
|
334
|
+
|
|
335
|
+
## Recommended Agent Workflow
|
|
336
|
+
|
|
337
|
+
1. **Read** manifest via `ark.manifest().toJSON()`
|
|
338
|
+
2. **Generate** code using registered intents, profiles, metadata, projections, and workflow definitions
|
|
339
|
+
3. **Validate snippets** with `createAICodeGate().validate(source, { layer })`
|
|
340
|
+
4. **Validate repository** with `ark-check --root . --config ark.config.json`
|
|
341
|
+
5. **Lint** with `ark-runtime-kernel/eslint` recommended rules
|
|
342
|
+
6. **Wire** relationships via `registry.define(..., { dependsOn, produces })`
|
|
343
|
+
7. **Register** event contracts before publishing in strict mode
|
|
344
|
+
8. **Observe** runtime via `bus.getTrace()`, `auditTrail.query()`, outbox records, projection checkpoints, and `ark.observability.report()`
|
package/docs/ai-gates.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Gating AI Agents with Ark
|
|
2
|
+
|
|
3
|
+
The write-path gate is what makes Ark different from every other architecture linter: generated code is validated against your architecture **before it lands on disk**, not after the PR is red.
|
|
4
|
+
|
|
5
|
+
Everything below uses the same `ark.config.json` as `ark-check` (CI) — one contract, enforced everywhere. Generate it once:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx ark-check --init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For guided setup with prompts, use:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx ark init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For non-interactive defaults, use:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx ark init --yes
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
You can also generate only the starter gate files for common agent runtimes and CI:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx ark-check --install-agent-gates
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The command writes templates for `.mcp.json`, Claude hooks, Cursor MCP/rules,
|
|
30
|
+
GitHub Actions, `AGENTS.md`, and a Codex TOML snippet under `docs/`. It skips
|
|
31
|
+
existing files unless you pass `--force`, so review and commit only the templates
|
|
32
|
+
that match your project.
|
|
33
|
+
|
|
34
|
+
## Claude Code — hook (recommended, hard block)
|
|
35
|
+
|
|
36
|
+
`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.
|
|
37
|
+
|
|
38
|
+
Add to your project's `.claude/settings.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"hooks": {
|
|
43
|
+
"PreToolUse": [
|
|
44
|
+
{
|
|
45
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
46
|
+
"hooks": [
|
|
47
|
+
{
|
|
48
|
+
"type": "command",
|
|
49
|
+
"command": "npx ark-mcp --hook --root \"$CLAUDE_PROJECT_DIR\" --config ark.config.json"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
That's the whole setup. Try asking the agent to import a persistence adapter from your domain layer:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Ark architecture gate blocked this write to src/domain/order.ts (layer: DomainModel):
|
|
62
|
+
- [FORBIDDEN_PATTERN] Forbidden pattern matched: /from ['"].*\/(infra|adapters|persistence|db)/i (line 1)
|
|
63
|
+
- [FORBIDDEN_IMPORT] Forbidden import target: "../adapters/persistence/pg-order-repository". (line 1)
|
|
64
|
+
Fix the violations and retry. The architecture contract is available as the ark://manifest MCP resource.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Claude Code — MCP server (contract discovery + on-demand validation)
|
|
68
|
+
|
|
69
|
+
The MCP server exposes two things agents can use proactively:
|
|
70
|
+
|
|
71
|
+
- **`ark://manifest`** (resource) — the machine-readable architecture contract (layers + rules), so the agent can read the architecture before generating code.
|
|
72
|
+
- **`validate_code`** (tool) — validates a snippet against the architecture on demand.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
claude mcp add ark -- npx ark-mcp --root . --config ark.config.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
or in `.mcp.json`:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"ark": {
|
|
84
|
+
"type": "stdio",
|
|
85
|
+
"command": "npx",
|
|
86
|
+
"args": ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use both: the MCP server for discovery, the hook for enforcement.
|
|
93
|
+
|
|
94
|
+
## Cursor
|
|
95
|
+
|
|
96
|
+
Cursor supports MCP servers (`.cursor/mcp.json`):
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"ark": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
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`:
|
|
110
|
+
|
|
111
|
+
```markdown
|
|
112
|
+
---
|
|
113
|
+
description: Ark architecture contract
|
|
114
|
+
alwaysApply: true
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
Before writing or editing any TypeScript source file, call the `validate_code`
|
|
118
|
+
tool from the `ark` MCP server with the full post-edit file content and its
|
|
119
|
+
path. If it reports violations, fix them before writing. The architecture
|
|
120
|
+
contract is available as the `ark://manifest` resource.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Your hard backstop in Cursor is CI: `ark-check` fails the PR on anything that slips through.
|
|
124
|
+
|
|
125
|
+
## OpenAI Codex CLI
|
|
126
|
+
|
|
127
|
+
`~/.codex/config.toml`:
|
|
128
|
+
|
|
129
|
+
```toml
|
|
130
|
+
[mcp_servers.ark]
|
|
131
|
+
command = "npx"
|
|
132
|
+
args = ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Same model as Cursor: MCP for discovery/validation, `ark-check` in CI as the hard gate.
|
|
136
|
+
|
|
137
|
+
## Any other agent runtime with shell hooks
|
|
138
|
+
|
|
139
|
+
If your runtime can run a shell command before file writes and pass the tool payload on stdin (Claude Code's PreToolUse contract), `ark-mcp --hook` works as-is. The contract:
|
|
140
|
+
|
|
141
|
+
- stdin: JSON `{ "tool_name": "Write|Edit|MultiEdit", "tool_input": { "file_path": ..., ... } }`
|
|
142
|
+
- exit `0` → allow; exit `2` → block, human-readable violations on stderr
|
|
143
|
+
- plumbing problems (no stdin, non-source files, files outside `--root`) never block
|
|
144
|
+
|
|
145
|
+
## ESLint (editor feedback)
|
|
146
|
+
|
|
147
|
+
For in-editor red squiggles on layer violations, add the ESLint plugin:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
// eslint.config.js
|
|
151
|
+
import ark from 'ark-runtime-kernel/eslint';
|
|
152
|
+
|
|
153
|
+
export default [ark.configs.recommended];
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## CI backstop
|
|
157
|
+
|
|
158
|
+
Whatever the agent side does, gate the merge:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
- run: npx ark-check --root . --config ark.config.json --strict-config
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Adopting Ark on an existing codebase with violations? Freeze them once and ratchet down:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npx ark-check --update-baseline # writes .ark-baseline.json — commit it
|
|
168
|
+
npx ark-check --baseline # only NEW violations fail
|
|
169
|
+
```
|
|
@@ -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>
|