arkgate 2.11.0 → 2.13.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 (77) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/README.md +70 -41
  3. package/bin/ark-check.mjs +95 -36
  4. package/bin/ark-mcp.mjs +11 -5
  5. package/bin/ark-shared.mjs +88 -56
  6. package/bin/ark.mjs +97 -29
  7. package/bin/lib/agent-gates.mjs +79 -2093
  8. package/bin/lib/architecture-scan.mjs +8 -0
  9. package/bin/lib/ci-and-commands.mjs +392 -0
  10. package/bin/lib/codex-home.mjs +7 -0
  11. package/bin/lib/config-contract.mjs +331 -0
  12. package/bin/lib/deploy-path.mjs +205 -0
  13. package/bin/lib/doctor-plan.mjs +43 -16
  14. package/bin/lib/enforcement-profiles.mjs +97 -0
  15. package/bin/lib/gate-files.mjs +223 -0
  16. package/bin/lib/hook-templates.mjs +99 -0
  17. package/bin/lib/host-support-matrix.mjs +77 -0
  18. package/bin/lib/install-migrate.mjs +473 -0
  19. package/bin/lib/mcp-adoption.mjs +455 -0
  20. package/bin/lib/open-html.mjs +75 -0
  21. package/bin/lib/presets.mjs +6 -2
  22. package/bin/lib/safety-diagnostics.mjs +31 -11
  23. package/bin/lib/skill-install.mjs +323 -0
  24. package/bin/lib/ts-resolve.mjs +2 -1
  25. package/bin/lib/typescript-host.mjs +88 -0
  26. package/bin/lib/weakest-link.mjs +417 -0
  27. package/bin/lib/write-path-capabilities.mjs +182 -0
  28. package/bin/lib/write-path-detect.mjs +101 -0
  29. package/dist/configContract-iBLxx5Tz.d.cts +53 -0
  30. package/dist/configContract-iBLxx5Tz.d.ts +53 -0
  31. package/dist/eslint/index.cjs +375 -13
  32. package/dist/eslint/index.cjs.map +1 -1
  33. package/dist/eslint/index.d.cts +30 -20
  34. package/dist/eslint/index.d.ts +30 -20
  35. package/dist/eslint/index.js +375 -13
  36. package/dist/eslint/index.js.map +1 -1
  37. package/dist/index.cjs +723 -61
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +95 -5
  40. package/dist/index.d.ts +95 -5
  41. package/dist/index.js +716 -61
  42. package/dist/index.js.map +1 -1
  43. package/dist/nestjs/index.cjs +150 -42
  44. package/dist/nestjs/index.cjs.map +1 -1
  45. package/dist/nestjs/index.d.cts +2 -1
  46. package/dist/nestjs/index.d.ts +2 -1
  47. package/dist/nestjs/index.js +150 -42
  48. package/dist/nestjs/index.js.map +1 -1
  49. package/dist/runtime/index.cjs +723 -61
  50. package/dist/runtime/index.cjs.map +1 -1
  51. package/dist/runtime/index.d.cts +3 -2
  52. package/dist/runtime/index.d.ts +3 -2
  53. package/dist/runtime/index.js +716 -61
  54. package/dist/runtime/index.js.map +1 -1
  55. package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
  56. package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
  57. package/docs/agent-guide.md +43 -21
  58. package/docs/ai-gates.md +53 -18
  59. package/docs/configuration.md +97 -0
  60. package/docs/enthusiast/README.md +3 -3
  61. package/docs/enthusiast/how-to-agent-gates.md +7 -3
  62. package/docs/migrate-from-ark-runtime-kernel.md +3 -0
  63. package/docs/package-surface.md +22 -10
  64. package/docs/production-hardening.md +15 -2
  65. package/docs/threat-model.md +65 -0
  66. package/docs/typescript-support.md +3 -3
  67. package/package.json +15 -2
  68. package/schemas/ark.config.schema.json +750 -0
  69. package/server.json +2 -2
  70. package/templates/hooks/pre-commit-ark +37 -0
  71. package/templates/skills/ark-autopilot.md +77 -45
  72. package/templates/skills/ark-coverage.md +2 -2
  73. package/templates/skills/ark-explain.md +2 -1
  74. package/templates/skills/ark-explore.md +135 -34
  75. package/templates/skills/ark-runtime.md +8 -5
  76. package/templates/skills/ark-upgrade.md +36 -16
  77. package/tests/fixtures/ts-consumer/ark.config.json +2 -0
package/bin/ark.mjs CHANGED
@@ -19,10 +19,37 @@ import {
19
19
  resolveOperatingMode,
20
20
  } from './ark-shared.mjs';
21
21
  import { pinArkgateDevDependency, FALSE_GREEN_GAP_ID } from './lib/field-install.mjs';
22
+ import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
22
23
 
23
24
  const here = path.dirname(fileURLToPath(import.meta.url));
24
25
  const arkCheck = path.join(here, 'ark-check.mjs');
25
26
 
27
+ /**
28
+ * Day-zero architecture picture: freeze origin under `.ark/reports/` as soon as
29
+ * `ark.config.json` exists — **before** agent docs, skills, CI templates, or cleanups.
30
+ * Idempotent: origin is written only once (`--report` archive semantics).
31
+ */
32
+ function freezeDayZeroOrigin(root) {
33
+ const configPath = path.join(root, 'ark.config.json');
34
+ if (!fs.existsSync(configPath)) {
35
+ console.log(
36
+ ` Skip origin freeze — no ark.config.json yet. After init: ${arkCommand(root, 'ark-check', '--report ark-report.html')}`
37
+ );
38
+ return;
39
+ }
40
+ const originJson = path.join(root, '.ark', 'reports', 'origin.json');
41
+ const already = fs.existsSync(originJson);
42
+ console.log(
43
+ already
44
+ ? 'Architecture origin already frozen (.ark/reports/origin.*) — leaving it untouched.'
45
+ : 'Freezing day-zero architecture picture (origin) before agent docs / gates…'
46
+ );
47
+ runArkCheck(
48
+ ['--root', root, '--config', 'ark.config.json', '--report', 'ark-report.html'],
49
+ { cwd: root }
50
+ );
51
+ }
52
+
26
53
  function parseArgs(argv) {
27
54
  const args = {
28
55
  command: undefined,
@@ -31,6 +58,7 @@ function parseArgs(argv) {
31
58
  force: false,
32
59
  strict: true,
33
60
  install: true,
61
+ requireWriteHook: undefined,
34
62
  help: false,
35
63
  version: false,
36
64
  };
@@ -55,6 +83,9 @@ function parseArgs(argv) {
55
83
  else if (arg === '--preset') args.preset = requireValue(arg, i++);
56
84
  else if (arg === '--archetype') args.archetype = requireValue(arg, i++);
57
85
  else if (arg === '--tools') args.tools = requireValue(arg, i++);
86
+ else if (arg === '--require-write-hook') {
87
+ args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
88
+ }
58
89
  else if (arg === '--help' || arg === '-h' || arg === 'help') args.help = true;
59
90
  else if (arg === '--version' || arg === '-V') args.version = true;
60
91
  else if (!arg.startsWith('-') && args.command === undefined) args.command = arg;
@@ -66,9 +97,9 @@ function parseArgs(argv) {
66
97
 
67
98
  function usage() {
68
99
  return `Usage:
69
- ark start [--root <project>] [--yes]
100
+ ark start [--root <project>] [--tools <list>] [--require-write-hook <host>] [--yes]
70
101
  ark init [--root <project>] [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture]
71
- [--archetype <playbook-id>] [--tools <list>] [--yes] [--force] [--no-strict]
102
+ [--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
72
103
  ark upgrade [--root <project>] [--no-install] [--no-strict]
73
104
 
74
105
  Commands:
@@ -93,6 +124,9 @@ Options:
93
124
  vertical-slice-product, ddd-bounded-contexts.
94
125
  --tools Comma-separated agents to gate (claude,cursor,codex,grok,windsurf,cline,copilot,kiro,roo,continue,gemini).
95
126
  Omit to auto-detect from each tool's config dir, falling back to claude+cursor+codex+grok.
127
+ --require-write-hook <host>
128
+ Require and verify a hard local write hook for Claude or Grok. Cursor/Codex are
129
+ advisory-write plus hard CI merge only; impossible requests fail before any write.
96
130
 
97
131
  Interactive mode (TTY, no --yes): asks what application shape you are building and maps it to a preset.
98
132
  Non-interactive (no TTY): uses the same defaults as --yes — never calls readline on a null interface.
@@ -177,7 +211,7 @@ async function upgrade(args) {
177
211
  }
178
212
  console.log('\n4/4 Verifying architecture…');
179
213
  return runArkCheck(
180
- ['--root', root, '--config', 'ark.config.json', '--strict-config'],
214
+ ['--root', root, '--config', 'ark.config.json', '--strict-merge'],
181
215
  { cwd: root }
182
216
  );
183
217
  }
@@ -330,11 +364,18 @@ async function init(args) {
330
364
  console.log('Skipped ark.config.json generation.');
331
365
  }
332
366
 
367
+ // Origin first: contract-on-tree picture before AGENTS.md / skills / CI templates.
368
+ console.log('');
369
+ freezeDayZeroOrigin(root);
370
+
333
371
  const installGates =
334
372
  nonInteractive || (await askYesNo(rl, 'Configure agent and CI gate templates?', true));
335
373
  if (installGates) {
336
374
  const gateArgs = ['--root', root, '--install-agent-gates'];
337
375
  if (args.tools) gateArgs.push('--tools', args.tools);
376
+ if (args.requireWriteHook) {
377
+ gateArgs.push('--require-write-hook', args.requireWriteHook);
378
+ }
338
379
  if (args.force) gateArgs.push('--force');
339
380
  const status = runArkCheck(gateArgs, { cwd: root });
340
381
  if (status !== 0) return status;
@@ -344,20 +385,21 @@ async function init(args) {
344
385
  args.strict &&
345
386
  (nonInteractive || (await askYesNo(rl, 'Run strict architecture check now?', true)));
346
387
  if (runStrict) {
347
- return runArkCheck(
348
- ['--root', root, '--config', 'ark.config.json', '--strict-config'],
349
- { cwd: root }
350
- );
388
+ const strictArgs = ['--root', root, '--config', 'ark.config.json', '--strict-merge'];
389
+ if (args.requireWriteHook) {
390
+ strictArgs.push('--require-write-hook', args.requireWriteHook);
391
+ }
392
+ return runArkCheck(strictArgs, { cwd: root });
351
393
  }
352
394
 
353
395
  console.log(
354
- `Ark init complete. Run \`${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-config')}\` before merging.`
396
+ `Ark init complete. Run \`${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-merge')}\` before merging.`
355
397
  );
356
398
  if (archetype) {
357
399
  console.log(`Shape: ${archetype}. Plan: ${arkCommand(root, 'ark-check', '--recommend')}`);
358
400
  }
359
401
  console.log(
360
- `Freeze day-one architecture snapshot: ${arkCommand(root, 'ark-check', '--report ark-report.html')} (writes .ark/reports/origin.* once).`
402
+ `Day-zero origin: .ark/reports/origin.* (frozen once; later --report shows evolution vs origin).`
361
403
  );
362
404
  console.log(`Adoption health: ${arkCommand(root, 'ark-check', '--doctor')}`);
363
405
  return 0;
@@ -381,9 +423,11 @@ async function start(args) {
381
423
  try {
382
424
  console.log("Let's set up Ark for your project.");
383
425
  console.log(
384
- "I'll look at your code, suggest a shape, set up the guardrails, and show you a plan."
426
+ "I'll walk the tree, freeze a day-zero architecture picture, then set up guardrails and show a plan."
427
+ );
428
+ console.log(
429
+ 'Nothing in your product code is changed — only Ark config, then origin snapshot, then agent/CI templates.'
385
430
  );
386
- console.log('Nothing in your code is changed — this only adds Ark configuration.');
387
431
  if (nonInteractive && !args.yes) {
388
432
  console.log(
389
433
  'Non-interactive session (no TTY) — using guided defaults (same as --yes). Pass flags to override.'
@@ -440,10 +484,10 @@ async function start(args) {
440
484
  console.log(' Skipping arkgate package pin (--no-install).');
441
485
  }
442
486
 
443
- // 3) Set up config + gates. Greenfield → the shape's preset; an established repo → detection,
444
- // so the contract anchors to the directories you already have instead of aspirational globs.
487
+ // 3) Contract first (config only). Greenfield → shape preset; established repo → detection,
488
+ // so the contract anchors to directories you already have instead of aspirational globs.
445
489
  console.log('');
446
- console.log('Setting up Ark…');
490
+ console.log('Setting up Ark contract…');
447
491
  const configPath = path.join(root, 'ark.config.json');
448
492
  if (!fs.existsSync(configPath)) {
449
493
  const initArgs = ['--root', root, '--init'];
@@ -482,14 +526,27 @@ async function start(args) {
482
526
  } else {
483
527
  console.log(' Found an existing ark.config.json — keeping it.');
484
528
  }
529
+
530
+ // 4) Day-zero origin — freeze the architecture picture *before* agent docs / CI / skills.
531
+ // Later --report runs show evolution vs this snapshot. Idempotent (origin once).
532
+ console.log('');
533
+ freezeDayZeroOrigin(root);
534
+
535
+ // 5) Agent + CI gate templates (docs, hooks, skills) — after origin is frozen.
536
+ console.log('');
537
+ console.log('Installing agent and CI gate templates…');
485
538
  {
486
539
  const gateArgs = ['--root', root, '--install-agent-gates'];
487
540
  if (args.tools) gateArgs.push('--tools', args.tools);
541
+ if (args.requireWriteHook) {
542
+ gateArgs.push('--require-write-hook', args.requireWriteHook);
543
+ }
488
544
  if (args.force) gateArgs.push('--force');
489
- runArkCheck(gateArgs, { cwd: root });
545
+ const status = runArkCheck(gateArgs, { cwd: root });
546
+ if (status !== 0) return status;
490
547
  }
491
548
 
492
- // 4) Show the plan: what's safe to auto-fix vs what needs a decision.
549
+ // 6) Show the plan: what's safe to auto-fix vs what needs a decision.
493
550
  console.log('');
494
551
  console.log('Your architecture plan:');
495
552
  runArkCheck(['--root', root, '--config', 'ark.config.json', '--plan'], { cwd: root });
@@ -571,7 +628,7 @@ async function start(args) {
571
628
  planOk = false;
572
629
  }
573
630
 
574
- // 5) Plain-language wrap-up — one next step, status light only.
631
+ // 7) Plain-language wrap-up — one next step, status light only.
575
632
  // Modes are detected (Suggest/Adapt/Enforce), not user-picked settings.
576
633
  // Soft-block false-green using the same doctor adoption gap (no second detector).
577
634
  let falseGreenGap = null;
@@ -620,26 +677,18 @@ async function start(args) {
620
677
  console.log(' → reclassify I/O dirs out of Application; then /ark-autopilot for residual debt.');
621
678
  } else {
622
679
  console.log(' 1. In your agent: /ark-autopilot');
623
- console.log(' → origin report, adoption, plan, safe fixes, leave gates on.');
680
+ console.log(' → explore first, dual plan (remediation + pattern bets), safe fixes, leave gates on.');
624
681
  }
625
682
  console.log(` 2. Status anytime: ${arkCommand(root, 'ark-check', '--doctor')}`);
626
- console.log(` 3. After edits: ${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-config')}`);
683
+ console.log(` 3. After edits: ${arkCommand(root, 'ark-check', '--root . --config ark.config.json --strict-merge')}`);
627
684
  if (mode === 'adapt' && planOk && !falseGreenGap) {
628
685
  console.log(
629
686
  ` 4. When green but cores still optional: ${arkCommand(root, 'ark-check', '--ratchet-cores')} → honest ENFORCE`
630
687
  );
631
688
  }
632
689
  console.log('');
633
- console.log('Optional later: --plan · --coverage · /ark-fix · /ark-place · ark upgrade');
634
-
635
- // 6) First architecture report — freezes an origin snapshot under .ark/reports/
636
- // so later --report runs can show evolution. Idempotent: origin is written only once.
637
- console.log('');
638
- console.log('Capturing architecture report (origin snapshot on first run)…');
639
- runArkCheck(
640
- ['--root', root, '--config', 'ark.config.json', '--report', 'ark-report.html'],
641
- { cwd: root }
642
- );
690
+ console.log('Day-zero origin is under .ark/reports/origin.* re-run --report later for evolution.');
691
+ console.log('Optional later: --plan · --coverage · /ark-explore · /ark-fix · /ark-place · ark upgrade');
643
692
  return 0;
644
693
  } finally {
645
694
  rl?.close();
@@ -663,6 +712,25 @@ async function main() {
663
712
  return 0;
664
713
  }
665
714
 
715
+ if (args.requireWriteHook && !['start', 'init'].includes(args.command)) {
716
+ console.error('--require-write-hook is supported by ark start and ark init.');
717
+ return 2;
718
+ }
719
+ const enforcement = validateHardWriteRequest({
720
+ root: args.root,
721
+ host: args.requireWriteHook,
722
+ tools: args.tools,
723
+ force: args.force,
724
+ });
725
+ if (!enforcement.ok) {
726
+ console.error(enforcement.error);
727
+ return 2;
728
+ }
729
+ if (enforcement.host) {
730
+ args.requireWriteHook = enforcement.host;
731
+ if (!args.tools) args.tools = enforcement.tools.join(',');
732
+ }
733
+
666
734
  if (args.command === 'start') {
667
735
  try {
668
736
  return await start(args);