ark-runtime-kernel 1.1.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 CHANGED
@@ -30,7 +30,7 @@ No code changes. No new runtime. Just a config and a CI line.
30
30
 
31
31
  ```bash
32
32
  npm install -D ark-runtime-kernel typescript
33
- npx ark-check --init # infers layers from your existing folders ark.config.json
33
+ npx ark init # asks before generating config, agent gates, and CI templates
34
34
  npx ark-check # done: cross-layer imports now fail the check
35
35
  ```
36
36
 
@@ -58,6 +58,19 @@ Then gate your agents (Claude Code shown; [Cursor / Codex / others](docs/ai-gate
58
58
 
59
59
  > The same `ark.config.json` powers every gate.
60
60
 
61
+ Or generate the starter agent and CI gate files:
62
+
63
+ ```bash
64
+ npx ark-check --install-agent-gates
65
+ ```
66
+
67
+ This writes opt-in templates for MCP discovery, Claude/Cursor rules, Codex config notes,
68
+ GitHub Actions, and agent instructions. Existing files are skipped unless you pass
69
+ `--force`.
70
+
71
+ The package `postinstall` only prints the next command; it never prompts or writes files
72
+ during `npm install`. Use `npx ark init --yes` for non-interactive setup.
73
+
61
74
  ## Why Ark (and not just a linter)?
62
75
 
63
76
  If you only need import-boundary linting in CI, [dependency-cruiser](https://github.com/sverweij/dependency-cruiser), [eslint-plugin-boundaries](https://github.com/javierbrea/eslint-plugin-boundaries), and Nx module boundaries are solid tools. Ark's reason to exist is the **write-time, agent-native half** they don't cover:
package/bin/ark-check.mjs CHANGED
@@ -22,6 +22,7 @@ function parseArgs(argv) {
22
22
  json: false,
23
23
  strictConfig: false,
24
24
  init: false,
25
+ installAgentGates: false,
25
26
  force: false,
26
27
  baseline: undefined,
27
28
  updateBaseline: false,
@@ -31,6 +32,7 @@ function parseArgs(argv) {
31
32
  if (arg === '--json') args.json = true;
32
33
  else if (arg === '--strict-config') args.strictConfig = true;
33
34
  else if (arg === '--init') args.init = true;
35
+ else if (arg === '--install-agent-gates') args.installAgentGates = true;
34
36
  else if (arg === '--force') args.force = true;
35
37
  else if (arg === '--baseline' || arg === '--update-baseline') {
36
38
  if (arg === '--update-baseline') args.updateBaseline = true;
@@ -52,6 +54,7 @@ function usage() {
52
54
  return [
53
55
  'Usage: ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict-config] [--json] [--baseline [file]]',
54
56
  ' ark-check --init [--force]',
57
+ ' ark-check --install-agent-gates [--force]',
55
58
  ' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
56
59
  ' ark-check --print-config eleven-layer',
57
60
  '',
@@ -82,6 +85,9 @@ function usage() {
82
85
  '',
83
86
  'Generate a starter 11-layer config:',
84
87
  ' ark-check --print-config eleven-layer > ark.config.json',
88
+ '',
89
+ 'Install agent + CI enforcement templates:',
90
+ ' ark-check --install-agent-gates',
85
91
  ].join('\n');
86
92
  }
87
93
 
@@ -205,6 +211,170 @@ function runInit(args) {
205
211
  console.log(' (bind its validate_code tool to your agent\'s pre-write hook — see README)');
206
212
  }
207
213
 
214
+ function ensureDirForFile(file) {
215
+ fs.mkdirSync(path.dirname(file), { recursive: true });
216
+ }
217
+
218
+ function writeTemplate(root, relativePath, content, force) {
219
+ const fullPath = path.join(root, relativePath);
220
+ if (fs.existsSync(fullPath) && !force) {
221
+ return { relativePath, status: 'skipped' };
222
+ }
223
+ ensureDirForFile(fullPath);
224
+ fs.writeFileSync(fullPath, content);
225
+ return { relativePath, status: fs.existsSync(fullPath) ? 'written' : 'written' };
226
+ }
227
+
228
+ function packageManager(root) {
229
+ if (fs.existsSync(path.join(root, 'pnpm-lock.yaml'))) {
230
+ return {
231
+ cache: 'pnpm',
232
+ setup: ['corepack enable'],
233
+ install: 'pnpm install --frozen-lockfile',
234
+ run: 'pnpm exec ark-check --root . --config ark.config.json --strict-config',
235
+ };
236
+ }
237
+ if (fs.existsSync(path.join(root, 'yarn.lock'))) {
238
+ return {
239
+ cache: 'yarn',
240
+ setup: ['corepack enable'],
241
+ install: 'yarn install --frozen-lockfile',
242
+ run: 'yarn ark-check --root . --config ark.config.json --strict-config',
243
+ };
244
+ }
245
+ return {
246
+ cache: 'npm',
247
+ setup: [],
248
+ install: fs.existsSync(path.join(root, 'package-lock.json')) ? 'npm ci' : 'npm install',
249
+ run: 'npx ark-check --root . --config ark.config.json --strict-config',
250
+ };
251
+ }
252
+
253
+ function agentInstructions() {
254
+ return `# Ark Enforcement
255
+
256
+ Before editing TypeScript or JavaScript source files:
257
+
258
+ 1. Read the Ark contract from \`ark://manifest\` when the MCP server is available.
259
+ 2. Keep source files inside the layer boundaries declared in \`ark.config.json\`.
260
+ 3. Do not bypass Ark publishers, event contracts, or source metadata for runtime mutations.
261
+ 4. After edits, run \`npx ark-check --root . --config ark.config.json --strict-config\`.
262
+ 5. If Ark reports violations, fix the architecture instead of weakening the gate.
263
+
264
+ The project is only considered Ark-enforced when the write gate, CI gate, and runtime path all pass.
265
+ `;
266
+ }
267
+
268
+ function mcpJson() {
269
+ return `${JSON.stringify({
270
+ mcpServers: {
271
+ ark: {
272
+ type: 'stdio',
273
+ command: 'npx',
274
+ args: ['ark-mcp', '--root', '.', '--config', 'ark.config.json'],
275
+ },
276
+ },
277
+ }, null, 2)}\n`;
278
+ }
279
+
280
+ function codexTomlSnippet() {
281
+ return `[mcp_servers.ark]
282
+ command = "npx"
283
+ args = ["ark-mcp", "--root", ".", "--config", "ark.config.json"]
284
+ `;
285
+ }
286
+
287
+ function cursorRule() {
288
+ return `---
289
+ description: Ark architecture contract
290
+ alwaysApply: true
291
+ ---
292
+
293
+ Before writing or editing TypeScript or JavaScript source files, read the
294
+ \`ark://manifest\` resource from the \`ark\` MCP server when available.
295
+
296
+ Validate the full post-edit file content with the \`validate_code\` tool before
297
+ writing whenever your runtime supports it. After edits, run:
298
+
299
+ \`\`\`bash
300
+ npx ark-check --root . --config ark.config.json --strict-config
301
+ \`\`\`
302
+
303
+ If Ark reports violations, fix the architecture instead of bypassing the gate.
304
+ `;
305
+ }
306
+
307
+ function githubWorkflow(pm) {
308
+ const setupSteps = pm.setup.map((command) => ` - run: ${command}`).join('\n');
309
+ return `name: Ark architecture gate
310
+
311
+ on:
312
+ pull_request:
313
+ push:
314
+ branches: [main, master]
315
+
316
+ jobs:
317
+ ark-check:
318
+ runs-on: ubuntu-latest
319
+ steps:
320
+ - uses: actions/checkout@v4
321
+ - uses: actions/setup-node@v4
322
+ with:
323
+ node-version: 20
324
+ cache: ${pm.cache}
325
+ ${setupSteps ? `${setupSteps}\n` : ''} - run: ${pm.install}
326
+ - run: ${pm.run}
327
+ `;
328
+ }
329
+
330
+ function claudeSettings() {
331
+ return `${JSON.stringify({
332
+ hooks: {
333
+ PreToolUse: [
334
+ {
335
+ matcher: 'Write|Edit|MultiEdit',
336
+ hooks: [
337
+ {
338
+ type: 'command',
339
+ command:
340
+ 'npx ark-mcp --hook --root "$CLAUDE_PROJECT_DIR" --config ark.config.json',
341
+ },
342
+ ],
343
+ },
344
+ ],
345
+ },
346
+ }, null, 2)}\n`;
347
+ }
348
+
349
+ function runInstallAgentGates(args) {
350
+ const root = args.root;
351
+ const pm = packageManager(root);
352
+ const templates = [
353
+ ['AGENTS.md', agentInstructions()],
354
+ ['.mcp.json', mcpJson()],
355
+ ['.cursor/mcp.json', mcpJson()],
356
+ ['.cursor/rules/ark.mdc', cursorRule()],
357
+ ['.claude/settings.json', claudeSettings()],
358
+ ['.github/workflows/ark-check.yml', githubWorkflow(pm)],
359
+ ['docs/ark-codex-config.toml', codexTomlSnippet()],
360
+ ];
361
+
362
+ const results = templates.map(([relativePath, content]) =>
363
+ writeTemplate(root, relativePath, content, args.force)
364
+ );
365
+
366
+ console.log('Ark agent gate templates:');
367
+ for (const result of results) {
368
+ const marker = result.status === 'written' ? 'wrote' : 'skipped';
369
+ console.log(` ${marker.padEnd(7)} ${result.relativePath}`);
370
+ }
371
+ console.log('');
372
+ console.log('Next steps:');
373
+ console.log(' 1. Review the generated files and commit the ones that match your tools.');
374
+ console.log(' 2. Run: npx ark-check --root . --config ark.config.json --strict-config');
375
+ console.log(' 3. Wire Codex manually from docs/ark-codex-config.toml if your host uses ~/.codex/config.toml.');
376
+ }
377
+
208
378
  function readManifest(root, manifestPath) {
209
379
  if (!manifestPath) return undefined;
210
380
  const fullPath = path.isAbsolute(manifestPath)
@@ -670,6 +840,10 @@ async function main() {
670
840
  runInit(args);
671
841
  return;
672
842
  }
843
+ if (args.installAgentGates) {
844
+ runInstallAgentGates(args);
845
+ return;
846
+ }
673
847
  if (args.printConfig) {
674
848
  if (args.printConfig !== 'eleven-layer') {
675
849
  console.error(`Unknown config profile: ${args.printConfig}`);
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ if (process.env.ARK_POSTINSTALL_SILENT === '1') {
4
+ process.exit(0);
5
+ }
6
+
7
+ console.log(`Ark installed, but not enforced yet.
8
+ Run: npx ark init
9
+
10
+ For non-interactive setup:
11
+ Run: npx ark init --yes
12
+ `);
package/bin/ark.mjs ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import readline from 'node:readline/promises';
7
+
8
+ const here = path.dirname(fileURLToPath(import.meta.url));
9
+ const arkCheck = path.join(here, 'ark-check.mjs');
10
+
11
+ function parseArgs(argv) {
12
+ const args = {
13
+ command: argv[2],
14
+ root: process.cwd(),
15
+ yes: false,
16
+ force: false,
17
+ strict: true,
18
+ help: false,
19
+ };
20
+
21
+ for (let i = 3; i < argv.length; i += 1) {
22
+ const arg = argv[i];
23
+ if (arg === '--root') args.root = path.resolve(argv[++i]);
24
+ else if (arg === '--yes' || arg === '-y') args.yes = true;
25
+ else if (arg === '--force') args.force = true;
26
+ else if (arg === '--no-strict') args.strict = false;
27
+ else if (arg === '--help' || arg === '-h') args.help = true;
28
+ }
29
+
30
+ return args;
31
+ }
32
+
33
+ function usage() {
34
+ return `Usage:
35
+ ark init [--root <project>] [--yes] [--force] [--no-strict]
36
+
37
+ Commands:
38
+ init Configure Ark project enforcement with explicit prompts.
39
+
40
+ Options:
41
+ --yes Non-interactive defaults: create config if needed, install gate templates, run strict check.
42
+ --force Allow generated files to overwrite existing files.
43
+ --no-strict Skip the final strict ark-check run.
44
+ `;
45
+ }
46
+
47
+ function runArkCheck(args, options = {}) {
48
+ const result = spawnSync(process.execPath, [arkCheck, ...args], {
49
+ cwd: options.cwd,
50
+ stdio: options.stdio ?? 'inherit',
51
+ encoding: 'utf8',
52
+ });
53
+ return result.status ?? 1;
54
+ }
55
+
56
+ async function askYesNo(rl, question, defaultYes = true) {
57
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
58
+ const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
59
+ if (!answer) return defaultYes;
60
+ return answer === 'y' || answer === 'yes';
61
+ }
62
+
63
+ async function init(args) {
64
+ const root = args.root;
65
+ const configPath = path.join(root, 'ark.config.json');
66
+ const rl = args.yes
67
+ ? null
68
+ : readline.createInterface({ input: process.stdin, output: process.stdout });
69
+
70
+ try {
71
+ let shouldInit = !fs.existsSync(configPath);
72
+ if (fs.existsSync(configPath)) {
73
+ shouldInit = args.force
74
+ ? true
75
+ : args.yes
76
+ ? false
77
+ : await askYesNo(rl, 'ark.config.json already exists. Regenerate it?', false);
78
+ }
79
+
80
+ if (shouldInit) {
81
+ const initArgs = ['--root', root, '--init'];
82
+ if (args.force) initArgs.push('--force');
83
+ const status = runArkCheck(initArgs, { cwd: root });
84
+ if (status !== 0) return status;
85
+ } else {
86
+ console.log('Skipped ark.config.json generation.');
87
+ }
88
+
89
+ const installGates = args.yes || await askYesNo(rl, 'Configure agent and CI gate templates?', true);
90
+ if (installGates) {
91
+ const gateArgs = ['--root', root, '--install-agent-gates'];
92
+ if (args.force) gateArgs.push('--force');
93
+ const status = runArkCheck(gateArgs, { cwd: root });
94
+ if (status !== 0) return status;
95
+ }
96
+
97
+ const runStrict =
98
+ args.strict && (args.yes || await askYesNo(rl, 'Run strict architecture check now?', true));
99
+ if (runStrict) {
100
+ return runArkCheck(
101
+ ['--root', root, '--config', 'ark.config.json', '--strict-config'],
102
+ { cwd: root }
103
+ );
104
+ }
105
+
106
+ console.log('Ark init complete. Run `npx ark-check --root . --config ark.config.json --strict-config` before merging.');
107
+ return 0;
108
+ } finally {
109
+ rl?.close();
110
+ }
111
+ }
112
+
113
+ async function main() {
114
+ const args = parseArgs(process.argv);
115
+ if (args.help || !args.command) {
116
+ console.log(usage());
117
+ return 0;
118
+ }
119
+
120
+ if (args.command === 'init') {
121
+ return init(args);
122
+ }
123
+
124
+ console.error(`Unknown command: ${args.command}`);
125
+ console.error(usage());
126
+ return 2;
127
+ }
128
+
129
+ process.exitCode = await main();
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/version.ts
4
- var version = "1.0.0";
4
+ var version = "1.2.0";
5
5
 
6
6
  // src/kernel/intent/IntentRegistry.ts
7
7
  var IntentRegistry = class {