arkgate 3.7.0 → 3.8.1

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 (79) hide show
  1. package/CHANGELOG.md +113 -1145
  2. package/README.md +59 -19
  3. package/bin/ark-check-runtime.mjs +1598 -0
  4. package/bin/ark-check.mjs +32 -1565
  5. package/bin/ark-layer-match.mjs +9 -4
  6. package/bin/ark-mcp-runtime.mjs +1976 -0
  7. package/bin/ark-mcp.mjs +84 -1495
  8. package/bin/ark-shared.mjs +34 -38
  9. package/bin/ark.mjs +33 -66
  10. package/bin/lib/adapter-contract.mjs +161 -9
  11. package/bin/lib/agent-gates.mjs +1 -0
  12. package/bin/lib/analysis-completeness.mjs +28 -0
  13. package/bin/lib/analysis-engine.mjs +8 -8
  14. package/bin/lib/analysis-policy.mjs +27 -0
  15. package/bin/lib/architecture-scan.mjs +70 -357
  16. package/bin/lib/auto-patch.mjs +76 -8
  17. package/bin/lib/ci-and-commands.mjs +1 -1
  18. package/bin/lib/codex-home.mjs +43 -16
  19. package/bin/lib/design-delta.mjs +4 -0
  20. package/bin/lib/doctor-advisories.mjs +4 -3
  21. package/bin/lib/doctor-plan.mjs +40 -41
  22. package/bin/lib/enforcement-state.mjs +2 -0
  23. package/bin/lib/github-enforcement.mjs +443 -0
  24. package/bin/lib/hook-templates.mjs +12 -148
  25. package/bin/lib/html-report-advisories.mjs +1 -1
  26. package/bin/lib/html-report-depth.mjs +9 -0
  27. package/bin/lib/html-report.mjs +5 -5
  28. package/bin/lib/install-migrate.mjs +83 -79
  29. package/bin/lib/managed-upgrade.mjs +622 -0
  30. package/bin/lib/mcp-adoption.mjs +3 -1
  31. package/bin/lib/parse-health.mjs +6 -5
  32. package/bin/lib/port-proof.mjs +2 -2
  33. package/bin/lib/prepare-change.mjs +68 -38
  34. package/bin/lib/prepare-write.mjs +7 -1
  35. package/bin/lib/resident-doctor-client.mjs +55 -0
  36. package/bin/lib/resident-hook.mjs +247 -0
  37. package/bin/lib/resolved-candidate-facts.mjs +1160 -0
  38. package/bin/lib/scan-files.mjs +19 -6
  39. package/bin/lib/snippet-analysis.mjs +119 -0
  40. package/bin/lib/source-policy.mjs +24 -0
  41. package/bin/lib/typescript-host.mjs +15 -18
  42. package/bin/lib/unavailable-analysis.mjs +76 -0
  43. package/bin/lib/upgrade-command.mjs +115 -0
  44. package/bin/lib/weakest-link.mjs +21 -179
  45. package/bin/lib/write-path-capabilities.mjs +167 -16
  46. package/bin/lib/write-path-detect.mjs +3 -2
  47. package/dist/eslint/index.cjs +3 -3
  48. package/dist/eslint/index.d.ts +4 -1
  49. package/dist/eslint/index.js +3 -3
  50. package/dist/index.cjs +6 -6
  51. package/dist/index.d.ts +1111 -151
  52. package/dist/index.js +7 -7
  53. package/docs/agent-guide.md +106 -62
  54. package/docs/ai-gates.md +97 -16
  55. package/docs/configuration.md +3 -0
  56. package/docs/demos/01-write-gate-self-correction.md +2 -2
  57. package/docs/enthusiast/README.md +10 -10
  58. package/docs/enthusiast/how-to-gallery-starter.md +2 -2
  59. package/docs/enthusiast/reference-commands.md +18 -1
  60. package/docs/enthusiast/tutorial-first-project.md +2 -2
  61. package/docs/package-surface.md +98 -12
  62. package/docs/typescript-support.md +108 -37
  63. package/package.json +32 -4
  64. package/schemas/ark.analysis-result.schema.json +159 -2
  65. package/schemas/ark.design-delta.schema.json +1 -0
  66. package/schemas/ark.enforcement-state.schema.json +84 -0
  67. package/schemas/ark.resolved-candidate-facts.schema.json +1 -0
  68. package/server.json +2 -2
  69. package/templates/skills/ark-explore.md +5 -5
  70. package/templates/skills/ark-fix.md +1 -1
  71. package/templates/skills/ark-runtime.md +15 -8
  72. package/templates/skills/ark-upgrade.md +122 -182
  73. package/bin/lib/ai-velocity.mjs +0 -293
  74. package/bin/lib/graph-cycles.mjs +0 -6
  75. package/bin/lib/safety-diagnostics.mjs +0 -284
  76. package/bin/lib/ts-resolve.mjs +0 -228
  77. package/dist/configTypes-DAPvBqK6.d.cts +0 -61
  78. package/dist/eslint/index.d.cts +0 -146
  79. package/dist/index.d.cts +0 -986
@@ -56,19 +56,27 @@ function isInsideRoot(root, target) {
56
56
  * while escaping links fail closed instead of reading arbitrary filesystem paths.
57
57
  */
58
58
  export function walk(dir, files = [], options = {}) {
59
- const state = options.state ?? {
60
- root: options.root ? fs.realpathSync(options.root) : undefined,
61
- visitedDirectories: new Set(),
62
- visitedFiles: new Set(),
63
- };
59
+ let state = options.state;
60
+ if (!state) {
61
+ if (options.root) options.observeInput?.(path.resolve(options.root), 'realpath');
62
+ state = {
63
+ root: options.root ? fs.realpathSync(options.root) : undefined,
64
+ visitedDirectories: new Set(),
65
+ visitedFiles: new Set(),
66
+ observeInput: options.observeInput,
67
+ };
68
+ }
69
+ state.observeInput?.(path.resolve(dir), 'lstat');
64
70
  const lstat = fs.lstatSync(dir, { throwIfNoEntry: false });
65
71
  if (!lstat) return files;
72
+ state.observeInput?.(path.resolve(dir), 'realpath');
66
73
  const resolved = fs.realpathSync(dir);
67
74
  if (state.root && !isInsideRoot(state.root, resolved)) {
68
75
  throw new Error(
69
76
  `Refusing to scan symlink outside project root: ${dir} -> ${resolved}`
70
77
  );
71
78
  }
79
+ if (lstat.isSymbolicLink()) state.observeInput?.(path.resolve(dir), 'stat');
72
80
  const stat = lstat.isSymbolicLink()
73
81
  ? fs.statSync(dir, { throwIfNoEntry: false })
74
82
  : lstat;
@@ -87,8 +95,10 @@ export function walk(dir, files = [], options = {}) {
87
95
  return files;
88
96
  }
89
97
  if (!stat.isDirectory()) return files;
98
+ state.onDirectory?.(dir, resolved);
90
99
  if (state.visitedDirectories.has(resolved)) return files;
91
100
  state.visitedDirectories.add(resolved);
101
+ state.observeInput?.(path.resolve(dir), 'directory');
92
102
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
93
103
  const full = path.join(dir, entry.name);
94
104
  if (entry.isDirectory()) {
@@ -105,11 +115,14 @@ export function walk(dir, files = [], options = {}) {
105
115
  }
106
116
 
107
117
  /** Walk include roots then drop codegen / config.exclude (universal scan filter). */
108
- export function collectGovernedFiles(root, config) {
118
+ export function collectGovernedFiles(root, config, options = {}) {
119
+ options.observeInput?.(path.resolve(root), 'realpath');
109
120
  const state = {
110
121
  root: fs.realpathSync(root),
111
122
  visitedDirectories: new Set(),
112
123
  visitedFiles: new Set(),
124
+ onDirectory: options.onDirectory,
125
+ observeInput: options.observeInput,
113
126
  };
114
127
  const raw = (config.include ?? []).flatMap((entry) =>
115
128
  walk(path.join(root, entry), [], { state })
@@ -0,0 +1,119 @@
1
+ /** Fail-closed completeness evidence for one proposed source snippet. */
2
+ import { ANALYSIS_COMPLETENESS } from './analysis-completeness.mjs';
3
+
4
+ function finding(ruleId, message, file, nextAction) {
5
+ return {
6
+ ruleId,
7
+ code: ruleId,
8
+ message,
9
+ ...(file ? { file, filePath: file } : {}),
10
+ nextAction,
11
+ };
12
+ }
13
+
14
+ export function validateSnippetAnalysis({ gate, ts, source, context = {} }) {
15
+ const observed = gate.validate(source, context);
16
+ const base = {
17
+ valid: Boolean(observed.lexicalValid ?? observed.valid),
18
+ violations: Array.isArray(observed.violations) ? observed.violations : [],
19
+ };
20
+ const file = context.filePath;
21
+
22
+ if (!ts || typeof ts.createSourceFile !== 'function') {
23
+ return {
24
+ mode: 'lexical-compatibility',
25
+ valid: false,
26
+ lexicalValid: false,
27
+ completeness: ANALYSIS_COMPLETENESS.unavailable,
28
+ completenessReasons: [
29
+ {
30
+ code: 'ANALYSIS_HOST_UNAVAILABLE',
31
+ message: 'No API-compatible TypeScript host parsed the proposed source.',
32
+ ...(file ? { file } : {}),
33
+ },
34
+ ],
35
+ violations: [
36
+ ...base.violations,
37
+ finding(
38
+ 'ANALYSIS_HOST_UNAVAILABLE',
39
+ 'Analysis unavailable: no API-compatible TypeScript host parsed the proposed source.',
40
+ file,
41
+ 'Restore ArkGate\'s TypeScript analysis host, then validate the complete source again.'
42
+ ),
43
+ ],
44
+ };
45
+ }
46
+
47
+ try {
48
+ const parsed = ts.createSourceFile(
49
+ file || 'generated.ts',
50
+ source,
51
+ ts.ScriptTarget.Latest,
52
+ true
53
+ );
54
+ if (!Array.isArray(parsed.parseDiagnostics)) throw new Error('parse diagnostics unavailable');
55
+ const diagnosticCount = parsed.parseDiagnostics.length;
56
+ if (diagnosticCount > 0) {
57
+ return {
58
+ mode: 'lexical-compatibility',
59
+ valid: false,
60
+ lexicalValid: false,
61
+ completeness: ANALYSIS_COMPLETENESS.partial,
62
+ completenessReasons: [
63
+ {
64
+ code: 'ANALYSIS_PARSE_INCOMPLETE',
65
+ message: `The proposed source has ${diagnosticCount} parse diagnostic(s).`,
66
+ ...(file ? { file } : {}),
67
+ },
68
+ ],
69
+ violations: [
70
+ ...base.violations,
71
+ finding(
72
+ 'ANALYSIS_PARSE_INCOMPLETE',
73
+ `Analysis partial: proposed source has ${diagnosticCount} parse diagnostic(s).`,
74
+ file,
75
+ 'Fix the syntax until the TypeScript parser reports zero diagnostics, then validate again.'
76
+ ),
77
+ ],
78
+ };
79
+ }
80
+ return {
81
+ ...base,
82
+ mode: 'lexical-compatibility',
83
+ valid: false,
84
+ lexicalValid: base.valid,
85
+ completeness: ANALYSIS_COMPLETENESS.partial,
86
+ completenessReasons: [
87
+ {
88
+ code: 'LEXICAL_EVIDENCE_INCOMPLETE',
89
+ message:
90
+ 'Single-file validation cannot prove project module resolution or complete candidate evidence; use ark_prepare_change for a parity-capable verdict.',
91
+ ...(file ? { file } : {}),
92
+ },
93
+ ],
94
+ };
95
+ } catch {
96
+ return {
97
+ mode: 'lexical-compatibility',
98
+ valid: false,
99
+ lexicalValid: false,
100
+ completeness: ANALYSIS_COMPLETENESS.unavailable,
101
+ completenessReasons: [
102
+ {
103
+ code: 'ANALYSIS_HOST_UNAVAILABLE',
104
+ message: 'The TypeScript host could not parse the proposed source.',
105
+ ...(file ? { file } : {}),
106
+ },
107
+ ],
108
+ violations: [
109
+ ...base.violations,
110
+ finding(
111
+ 'ANALYSIS_HOST_UNAVAILABLE',
112
+ 'Analysis unavailable: the TypeScript host could not parse the proposed source.',
113
+ file,
114
+ 'Restore ArkGate\'s TypeScript analysis host, then validate the complete source again.'
115
+ ),
116
+ ],
117
+ };
118
+ }
119
+ }
@@ -12,6 +12,30 @@ export const SOURCE_POLICY_MESSAGES = {
12
12
  RAW_EVENT_PUBLISH: 'Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.',
13
13
  PUBLISH_MISSING_SOURCE: 'Strict Ark publish calls must include metadata.source.',
14
14
  };
15
+ export const DEFAULT_INTENT_PREFIXES = Object.freeze([
16
+ { layer: 'DomainModel', prefixes: ['Domain.'] },
17
+ { layer: 'ApplicationOrchestration', prefixes: ['Application.'] },
18
+ { layer: 'PersistenceAdapters', prefixes: ['Adapter.Persistence.', 'Adapter.Repository.'] },
19
+ { layer: 'IntegrationAdapters', prefixes: ['Adapter.Integration.', 'Adapter.External.'] },
20
+ { layer: 'WorkflowSagaEngine', prefixes: ['Workflow.'] },
21
+ { layer: 'BackgroundJobsScheduling', prefixes: ['Job.'] },
22
+ { layer: 'PresentationAdapters', prefixes: ['Presentation.', 'Adapter.Presentation.', 'Adapter.Api.'] },
23
+ { layer: 'ReportingReadModels', prefixes: ['Reporting.'] },
24
+ { layer: 'ExtensibilityMetadata', prefixes: ['Metadata.'] },
25
+ { layer: 'SecurityAuditObservability', prefixes: ['Security.', 'Audit.', 'Observability.'] },
26
+ { layer: 'Kernel', prefixes: ['Kernel.'] },
27
+ ]);
28
+ /** Longest matching prefix wins; declaration order resolves an identical-prefix tie. */
29
+ export function resolveIntentLayer(intent, layers) {
30
+ const candidates = layers.flatMap((layer, layerIndex) => (layer.prefixes ?? layer.intentPrefixes ?? []).map((prefix) => ({
31
+ layer: layer.name,
32
+ layerIndex,
33
+ prefix: prefix.endsWith('.') ? prefix : `${prefix}.`,
34
+ })));
35
+ return candidates
36
+ .filter(({ prefix }) => intent.startsWith(prefix))
37
+ .sort((left, right) => right.prefix.length - left.prefix.length || left.layerIndex - right.layerIndex)[0]?.layer;
38
+ }
15
39
  export function looksLikeArkIntent(value) {
16
40
  return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(value);
17
41
  }
@@ -2,11 +2,10 @@
2
2
  * TypeScript host resolution for architecture scan (API-compatible loader).
3
3
  */
4
4
  import path from 'node:path';
5
+ import { createRequire } from 'node:module';
5
6
  import { usableTypescript, typescriptUsabilityHint } from '../ark-shared.mjs';
6
- import { __arkCheckCli } from './gate-files.mjs';
7
7
 
8
8
  export async function loadTypeScript(root) {
9
- const { createRequire } = await import('node:module');
10
9
  const loaders = [];
11
10
  try {
12
11
  const req = createRequire(path.join(root, 'package.json'));
@@ -24,15 +23,15 @@ export async function loadTypeScript(root) {
24
23
  } catch {
25
24
  /* project has no package.json resolvable tree */
26
25
  }
27
- // Nested under arkgate (production dependency) must work when project has only TS7.
26
+ // Dedicated production fallback: a different package identity cannot dedupe to project TS7.
28
27
  try {
29
- const req = createRequire(__arkCheckCli);
28
+ const req = createRequire(import.meta.url);
30
29
  loaders.push({
31
- label: 'arkgate',
32
- load: () => req('typescript'),
30
+ label: 'arkgate-fallback',
31
+ load: () => req('typescript-ark-host'),
33
32
  resolvePath: () => {
34
33
  try {
35
- return req.resolve('typescript');
34
+ return req.resolve('typescript-ark-host');
36
35
  } catch {
37
36
  return null;
38
37
  }
@@ -41,15 +40,6 @@ export async function loadTypeScript(root) {
41
40
  } catch {
42
41
  /* ark install tree unavailable */
43
42
  }
44
- loaders.push({
45
- label: 'import',
46
- load: async () => {
47
- const m = await import('typescript');
48
- return m;
49
- },
50
- resolvePath: () => null,
51
- });
52
-
53
43
  let projectRejected = null;
54
44
  const triedPaths = new Set();
55
45
  for (const { label, load, resolvePath } of loaders) {
@@ -74,15 +64,22 @@ export async function loadTypeScript(root) {
74
64
  ts,
75
65
  source: label,
76
66
  version,
67
+ ...(resolved ? { resolvedPath: resolved } : {}),
77
68
  ...(projectRejected ? { fallbackReason: projectRejected } : {}),
78
69
  };
79
70
  }
80
71
  if (label === 'project' && mod) {
81
- projectRejected = `project typescript is not API-compatible (${typescriptUsabilityHint(mod)}); using ArkGate's JS-API TypeScript fallback (TypeScript 7.0 main export is version-only). See docs/typescript-support.md.`;
72
+ projectRejected = `project TypeScript is not API-compatible (${typescriptUsabilityHint(mod)}); using ArkGate's physically independent TypeScript 6 JS-API host. See docs/typescript-support.md.`;
82
73
  }
83
74
  } catch {
84
75
  /* try next loader */
85
76
  }
86
77
  }
87
- return null;
78
+ return {
79
+ ts: null,
80
+ source: 'unavailable',
81
+ reason:
82
+ 'ArkGate could not load either the project TypeScript API or its independent TypeScript 6 fallback.',
83
+ ...(projectRejected ? { fallbackReason: projectRejected } : {}),
84
+ };
88
85
  }
@@ -0,0 +1,76 @@
1
+ /** Report TypeScript-host unavailability without a false-green architecture result. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { computeCoverage, runDoctor, runPlan } from './doctor-plan.mjs';
5
+ import { ANALYSIS_COMPLETENESS } from './analysis-completeness.mjs';
6
+ import { summarizeParseHealth } from './parse-health.mjs';
7
+
8
+ export function reportUnavailableAnalysis({
9
+ root,
10
+ config,
11
+ rules,
12
+ files,
13
+ args,
14
+ message,
15
+ nextAction,
16
+ createResult,
17
+ }) {
18
+ const completeness = ANALYSIS_COMPLETENESS.unavailable;
19
+ const parseHealth = summarizeParseHealth();
20
+ const finding = {
21
+ ruleId: 'ANALYSIS_HOST_UNAVAILABLE',
22
+ message,
23
+ nextAction,
24
+ file: '<analysis-host>',
25
+ };
26
+
27
+ if (args.plan) {
28
+ const coverage = computeCoverage(root, config, files, rules);
29
+ runPlan(root, [], args.json, coverage.governed.percent, coverage.governed.totalFiles, {
30
+ config,
31
+ files,
32
+ coverage,
33
+ completeness,
34
+ });
35
+ process.exitCode = 2;
36
+ return;
37
+ }
38
+
39
+ if (args.doctor) {
40
+ const configPath = path.isAbsolute(args.config) ? args.config : path.join(root, args.config);
41
+ runDoctor(root, config, files, rules, [], args.json, {
42
+ configPath,
43
+ configMissing: !fs.existsSync(configPath),
44
+ parseHealth,
45
+ completeness,
46
+ });
47
+ process.exitCode = 2;
48
+ return;
49
+ }
50
+
51
+ const adapterResult = createResult({
52
+ valid: false,
53
+ completeness,
54
+ completenessReasons: [
55
+ {
56
+ code: finding.ruleId,
57
+ message: finding.message,
58
+ file: finding.file,
59
+ },
60
+ ],
61
+ mode: 'resolved-candidate-facts',
62
+ violations: [finding],
63
+ });
64
+ if (args.json) {
65
+ console.log(JSON.stringify({
66
+ ...adapterResult,
67
+ ok: false,
68
+ violations: [finding],
69
+ warnings: [],
70
+ }, null, 2));
71
+ } else {
72
+ console.error(message);
73
+ console.error(`Next action: ${nextAction}`);
74
+ }
75
+ process.exitCode = 2;
76
+ }
@@ -0,0 +1,115 @@
1
+ /** Read-only-first `ark upgrade` orchestration. Managed identity logic lives separately. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+
6
+ import {
7
+ applyManagedUpgrade,
8
+ managedUpgradeJson,
9
+ planManagedUpgrade,
10
+ renderManagedUpgrade,
11
+ } from './managed-upgrade.mjs';
12
+
13
+ function installedCli(root) {
14
+ const requireFromProject = createRequire(path.join(root, 'package.json'));
15
+ const packageJson = requireFromProject.resolve('arkgate/package.json');
16
+ return path.join(path.dirname(packageJson), 'bin', 'ark.mjs');
17
+ }
18
+
19
+ function previewArgs(args) {
20
+ const next = ['upgrade', '--root', args.root, '--no-install'];
21
+ if (args.tools) next.push('--tools', args.tools);
22
+ if (args.acceptConflicts) next.push('--accept-conflicts');
23
+ if (!args.strict) next.push('--no-strict');
24
+ if (args.json) next.push('--json');
25
+ return next;
26
+ }
27
+
28
+ function quote(value) {
29
+ const text = String(value);
30
+ return /^[A-Za-z0-9_./:@=-]+$/.test(text) ? text : `'${text.replace(/'/g, `'"'"'`)}'`;
31
+ }
32
+
33
+ function nextCommand(args, planDigest) {
34
+ const parts = ['ark', 'upgrade', '--apply', '--root', args.root];
35
+ if (!args.install) parts.push('--no-install');
36
+ if (!args.install && planDigest) parts.push('--plan-digest', planDigest);
37
+ if (args.tools) parts.push('--tools', args.tools);
38
+ if (args.acceptConflicts) parts.push('--accept-conflicts');
39
+ if (!args.strict) parts.push('--no-strict');
40
+ if (args.json) parts.push('--json');
41
+ return parts.map(quote).join(' ');
42
+ }
43
+
44
+ function verify(root, json, arkCheck, runArkCheck) {
45
+ const args = ['--root', root, '--config', 'ark.config.json', '--strict-merge'];
46
+ if (!json) return { exitCode: runArkCheck(args, { cwd: root }) };
47
+ const result = spawnSync(process.execPath, [arkCheck, ...args, '--json'], {
48
+ cwd: root,
49
+ stdio: ['ignore', 'pipe', 'pipe'],
50
+ encoding: 'utf8',
51
+ });
52
+ return { exitCode: result.status ?? 1, stderr: result.stderr?.trim() || undefined };
53
+ }
54
+
55
+ export function runUpgradeCommand(args, dependencies) {
56
+ const root = args.root;
57
+ if (args.apply && args.install) {
58
+ const [command, commandArgs] = dependencies.packageInstallArgv(root);
59
+ if (!args.json) console.log(`Updating ArkGate: ${command} ${commandArgs.join(' ')}`);
60
+ const install = spawnSync(command, commandArgs, {
61
+ cwd: root,
62
+ stdio: args.json ? ['ignore', 'pipe', 'pipe'] : 'inherit',
63
+ encoding: 'utf8',
64
+ });
65
+ const exitCode = install.status ?? 1;
66
+ if (exitCode !== 0) {
67
+ if (args.json && install.stderr) console.error(install.stderr.trim());
68
+ console.error(
69
+ `Package update failed (exit ${exitCode}). Fix the install and re-run, or use ` +
70
+ '`ark upgrade --no-install` against the installed version.'
71
+ );
72
+ return exitCode;
73
+ }
74
+ return spawnSync(process.execPath, [installedCli(root), ...previewArgs(args)], {
75
+ cwd: root,
76
+ stdio: 'inherit',
77
+ encoding: 'utf8',
78
+ }).status ?? 1;
79
+ }
80
+
81
+ const plan = planManagedUpgrade(root, {
82
+ tools: args.tools,
83
+ acceptConflicts: args.acceptConflicts,
84
+ });
85
+ if (!args.apply) {
86
+ const command = nextCommand(args, plan.planDigest);
87
+ if (args.json) console.log(managedUpgradeJson(plan, { nextCommand: command }));
88
+ else {
89
+ renderManagedUpgrade(plan, {
90
+ next: args.install
91
+ ? `Update the package and recompute this preview with: ${command}`
92
+ : `Apply the exact preview with: ${command}`,
93
+ });
94
+ }
95
+ return 0;
96
+ }
97
+
98
+ const applied = applyManagedUpgrade(root, plan, args.planDigest);
99
+ if (applied.blocked) {
100
+ if (args.json) console.log(JSON.stringify(applied, null, 2));
101
+ else renderManagedUpgrade(applied, {
102
+ next: 'Preview again with --accept-conflicts, then use that preview\'s exact next command.',
103
+ });
104
+ return 1;
105
+ }
106
+ const verification = args.strict
107
+ ? { mode: 'strict-merge', ...verify(root, args.json, dependencies.arkCheck, dependencies.runArkCheck) }
108
+ : { mode: 'skipped', exitCode: 0 };
109
+ if (args.json) console.log(JSON.stringify({ ...applied, verification }, null, 2));
110
+ else {
111
+ renderManagedUpgrade(applied);
112
+ if (!args.strict) console.log('Architecture verification skipped (--no-strict).');
113
+ }
114
+ return verification.exitCode;
115
+ }