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
@@ -6,26 +6,18 @@ import {
6
6
  withArkConfigMetadata,
7
7
  } from './lib/config-contract.mjs';
8
8
  import { collectForbiddenCapabilityUses } from './lib/analysis-engine.mjs';
9
- import { looksLikeArkIntent } from './lib/source-policy.mjs';
9
+ import {
10
+ DEFAULT_INTENT_PREFIXES as DOMAIN_DEFAULT_INTENT_PREFIXES,
11
+ looksLikeArkIntent,
12
+ resolveIntentLayer as resolveConfiguredIntentLayer,
13
+ } from './lib/source-policy.mjs';
10
14
 
11
15
  /**
12
16
  * Default intent-prefix map shared by both CLIs and the ark-mcp write-path gate. The rule
13
17
  * matrix comes from the generated Domain config contract above. Prefixes mirror the runtime
14
18
  * profile but stay in this standalone CLI module because the CLIs run without a build step.
15
19
  */
16
- export const DEFAULT_INTENT_PREFIXES = [
17
- { layer: 'DomainModel', prefixes: ['Domain.'] },
18
- { layer: 'ApplicationOrchestration', prefixes: ['Application.'] },
19
- { layer: 'PersistenceAdapters', prefixes: ['Adapter.Persistence.', 'Adapter.Repository.'] },
20
- { layer: 'IntegrationAdapters', prefixes: ['Adapter.Integration.', 'Adapter.External.'] },
21
- { layer: 'WorkflowSagaEngine', prefixes: ['Workflow.'] },
22
- { layer: 'BackgroundJobsScheduling', prefixes: ['Job.'] },
23
- { layer: 'PresentationAdapters', prefixes: ['Presentation.', 'Adapter.Presentation.', 'Adapter.Api.'] },
24
- { layer: 'ReportingReadModels', prefixes: ['Reporting.'] },
25
- { layer: 'ExtensibilityMetadata', prefixes: ['Metadata.'] },
26
- { layer: 'SecurityAuditObservability', prefixes: ['Security.', 'Audit.', 'Observability.'] },
27
- { layer: 'Kernel', prefixes: ['Kernel.'] },
28
- ];
20
+ export const DEFAULT_INTENT_PREFIXES = DOMAIN_DEFAULT_INTENT_PREFIXES;
29
21
 
30
22
  export const DEFAULT_LAYER_DIRECTORIES = {
31
23
  DomainModel: ['domain'],
@@ -447,10 +439,6 @@ export {
447
439
  isScanExcludedRelative,
448
440
  } from './ark-layer-match.mjs';
449
441
 
450
- function normalizePrefix(prefix) {
451
- return prefix.endsWith('.') ? prefix : `${prefix}.`;
452
- }
453
-
454
442
  /**
455
443
  * Resolve an intent name to its layer using the SAME semantics as
456
444
  * ArchitectureProfile.resolveLayer in src/kernel/layers/ArchitectureProfile.ts (which the
@@ -460,16 +448,7 @@ function normalizePrefix(prefix) {
460
448
  * the write-path gate classify identically. `layers` is an array of { name, prefixes }.
461
449
  */
462
450
  export function resolveIntentLayer(intent, layers) {
463
- const normalized = layers.map((layer) => ({
464
- name: layer.name,
465
- prefixes: (layer.prefixes ?? []).map(normalizePrefix),
466
- }));
467
- const sorted = [...normalized].sort((a, b) => {
468
- const maxA = Math.max(0, ...a.prefixes.map((p) => p.length));
469
- const maxB = Math.max(0, ...b.prefixes.map((p) => p.length));
470
- return maxB - maxA;
471
- });
472
- return sorted.find((layer) => layer.prefixes.some((prefix) => intent.startsWith(prefix)))?.name;
451
+ return resolveConfiguredIntentLayer(intent, layers);
473
452
  }
474
453
 
475
454
  export function looksLikeIntent(value) {
@@ -489,7 +468,8 @@ export {
489
468
 
490
469
  /**
491
470
  * Normalize a required/imported TypeScript module for ark-check's host.
492
- * TS 5/6 expose `sys` on the root export. Early TS 7 / some ESM interop shapes
471
+ * TS 5/6 expose `sys` and the AST helpers used by the scanner on the root export. Early TS 7 /
472
+ * some ESM interop shapes
493
473
  * may nest under `.default` or omit `sys` — those are unusable for resolve/scan
494
474
  * and must fall through to a JS-API-compatible TypeScript (Ark's own or 5/6).
495
475
  *
@@ -508,7 +488,8 @@ export function usableTypescript(mod) {
508
488
  ts.sys &&
509
489
  typeof ts.sys.fileExists === 'function' &&
510
490
  typeof ts.createSourceFile === 'function' &&
511
- typeof ts.resolveModuleName === 'function'
491
+ typeof ts.resolveModuleName === 'function' &&
492
+ typeof ts.isInTypeQuery === 'function'
512
493
  ) {
513
494
  return ts;
514
495
  }
@@ -537,6 +518,9 @@ export function typescriptUsabilityHint(mod) {
537
518
  if (typeof ts.sys.fileExists !== 'function') return 'ts.sys.fileExists is not a function';
538
519
  if (typeof ts.createSourceFile !== 'function') return 'missing createSourceFile (AST API)';
539
520
  if (typeof ts.resolveModuleName !== 'function') return 'missing resolveModuleName';
521
+ if (typeof ts.isInTypeQuery !== 'function') {
522
+ return 'missing isInTypeQuery (requires a TypeScript 5/6 analysis host)';
523
+ }
540
524
  return 'unknown shape incompatibility';
541
525
  }
542
526
 
@@ -1732,14 +1716,26 @@ export function formatArchitectureRecommendationHuman(recommendation) {
1732
1716
 
1733
1717
  export const ADOPTION_PLAN_FILENAME = 'ark-adoption-plan.json';
1734
1718
 
1735
- const GALLERY_STARTER_BY_ARCHETYPE = {
1736
- 'crud-product': 'examples/crud-product-starter/',
1737
- 'api-backend': 'examples/api-backend-starter/',
1738
- 'worker-pipeline': 'examples/worker-pipeline-starter/',
1739
- 'multi-app-workspace': 'examples/multi-app-workspace-starter/',
1740
- 'vertical-slice-product': 'examples/vertical-slice-starter/',
1741
- 'ddd-bounded-contexts': 'examples/ddd-context-starter/',
1742
- };
1719
+ function galleryStarter(archetype, directory = archetype, generatedPreset) {
1720
+ return Object.freeze({
1721
+ archetype,
1722
+ directory: `examples/${directory}-starter`,
1723
+ ...(generatedPreset ? { generatedPreset } : {}),
1724
+ });
1725
+ }
1726
+
1727
+ export const GALLERY_STARTERS = Object.freeze([
1728
+ galleryStarter('crud-product'),
1729
+ galleryStarter('api-backend'),
1730
+ galleryStarter('worker-pipeline'),
1731
+ galleryStarter('multi-app-workspace'),
1732
+ galleryStarter('vertical-slice-product', 'vertical-slice', 'vertical-slice'),
1733
+ galleryStarter('ddd-bounded-contexts', 'ddd-context', 'ddd-bounded-contexts'),
1734
+ ]);
1735
+
1736
+ const GALLERY_STARTER_BY_ARCHETYPE = Object.fromEntries(
1737
+ GALLERY_STARTERS.map(({ archetype, directory }) => [archetype, `${directory}/`])
1738
+ );
1743
1739
 
1744
1740
  /** Enthusiast pack id for a named preset, or null when none ships. */
1745
1741
  export function policyPackIdForPreset(preset) {
package/bin/ark.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
- import os from 'node:os';
5
4
  import path from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
7
6
  import readline from 'node:readline/promises';
@@ -21,8 +20,10 @@ import {
21
20
  import { pinArkgateDevDependency, FALSE_GREEN_GAP_ID } from './lib/field-install.mjs';
22
21
  import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
23
22
  import { applyStartPreview, planStart, renderStartPreview } from './lib/start-preview.mjs';
23
+ import { runUpgradeCommand } from './lib/upgrade-command.mjs';
24
24
  import { detectActiveAgentHost } from './lib/skill-install.mjs';
25
25
  import { loadArkConfigContract } from './lib/config-contract.mjs';
26
+ import { loadTypeScript } from './lib/typescript-host.mjs';
26
27
  import {
27
28
  prepareChangeFromRoot,
28
29
  readChangeMapFile,
@@ -70,6 +71,8 @@ function parseArgs(argv) {
70
71
  install: true,
71
72
  installExplicit: false,
72
73
  apply: false,
74
+ acceptConflicts: false,
75
+ planDigest: undefined,
73
76
  json: false,
74
77
  internalApply: false,
75
78
  skipPackageManager: false,
@@ -104,6 +107,8 @@ function parseArgs(argv) {
104
107
  args.installExplicit = true;
105
108
  }
106
109
  else if (arg === '--apply') args.apply = true;
110
+ else if (arg === '--accept-conflicts') args.acceptConflicts = true;
111
+ else if (arg === '--plan-digest') args.planDigest = requireValue(arg, i++);
107
112
  else if (arg === '--json') args.json = true;
108
113
  else if (arg === '--internal-apply') args.internalApply = true;
109
114
  else if (arg === '--skip-package-manager') args.skipPackageManager = true;
@@ -112,6 +117,8 @@ function parseArgs(argv) {
112
117
  else if (arg === '--config') args.config = requireValue(arg, i++);
113
118
  else if (arg === '--changes') args.changes = requireValue(arg, i++);
114
119
  else if (arg === '--change-map') args.changeMap = requireValue(arg, i++);
120
+ else if (arg === '--manifest') args.manifest = requireValue(arg, i++);
121
+ else if (arg === '--tsconfig') args.tsconfig = requireValue(arg, i++);
115
122
  else if (arg === '--archetype') args.archetype = requireValue(arg, i++);
116
123
  else if (arg === '--tools') args.tools = requireValue(arg, i++);
117
124
  else if (arg === '--require-write-hook') {
@@ -131,17 +138,17 @@ function usage() {
131
138
  ark start [--root <project>] [--tools <host>] [--require-write-hook <host>] [--install] [--apply] [--json]
132
139
  ark init [--root <project>] [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture]
133
140
  [--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
134
- ark upgrade [--root <project>] [--no-install] [--no-strict]
135
- ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--json]
141
+ ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
142
+ ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]
136
143
 
137
144
  Commands:
138
145
  start New here? Analyze and preview the complete setup. Read-only unless --apply.
139
146
  init Configure Ark project enforcement with explicit prompts.
140
- upgrade One command to update Ark: bump the package to @latest, refresh gate
141
- templates + /ark-* skills (and Codex home prompts), migrate command
142
- runners to this project's package manager, then run the strict check.
143
- preflight Validate one atomic create/update/delete set without writing project files.
147
+ upgrade Preview identity-proven Ark-managed asset updates. With package install,
148
+ --apply bumps to @latest and recomputes the preview; a second explicit
149
+ --apply --no-install applies those exact bytes and verifies them.
144
150
  (alias: ark update)
151
+ preflight Validate one atomic create/update/delete set without writing project files.
145
152
 
146
153
  Options:
147
154
  --yes Non-interactive defaults: create config if needed, install gate templates, run strict check.
@@ -150,8 +157,11 @@ Options:
150
157
  --no-strict Skip the final strict ark-check run.
151
158
  --install Add arkgate to package.json explicitly before applying a start plan.
152
159
  --no-install Skip adding/installing arkgate as a project devDependency (start/upgrade).
153
- --apply Apply the mutations shown by the start preview.
154
- --json Emit the start preview as deterministic machine-readable JSON.
160
+ --apply Apply a start plan; for upgrade, update/repreview or apply with --no-install.
161
+ --accept-conflicts
162
+ Allow upgrade to recreate deleted managed assets or replace recorded conflicts.
163
+ --plan-digest Digest emitted by an upgrade preview; required to apply managed bytes.
164
+ --json Emit the start/upgrade preview as deterministic machine-readable JSON.
155
165
  --preset Start from a named architecture preset instead of detection.
156
166
  --archetype Application shape from templates/architecture-playbook.json (maps to the matching preset).
157
167
  Valid ids: crud-product, api-backend, frontend-surface, library-sdk, cli-utility,
@@ -199,60 +209,6 @@ function runCommand(command, commandArgs, cwd) {
199
209
  return result.status ?? 1;
200
210
  }
201
211
 
202
- // `ark upgrade`: the one command that replaces the "install @latest && install-agent-gates
203
- // --skills-only --force && ... --codex-home --force && ... --migrate-commands && check" chain.
204
- // Each step reruns ark-check as a fresh process, so the refresh runs from the freshly-installed
205
- // version, not this (now-older) process.
206
- async function upgrade(args) {
207
- const root = args.root;
208
- console.log('Ark upgrade — updating the package, gates, skills, and command runners.');
209
-
210
- if (args.install) {
211
- const [command, commandArgs] = packageInstallArgv(root);
212
- console.log(`\n1/4 Updating the package: ${command} ${commandArgs.join(' ')}`);
213
- const status = runCommand(command, commandArgs, root);
214
- if (status !== 0) {
215
- console.error(
216
- `\nPackage update failed (exit ${status}). Fix the install and re-run, or use ` +
217
- '`ark upgrade --no-install` to refresh gates/skills against the installed version.'
218
- );
219
- return status;
220
- }
221
- } else {
222
- console.log('\n1/4 Skipping package install (--no-install).');
223
- }
224
-
225
- console.log('\n2/4 Refreshing agent gates + /ark-* skills…');
226
- let status = runArkCheck(['--root', root, '--install-agent-gates'], { cwd: root });
227
- if (status !== 0) return status;
228
-
229
- // Codex home skill catalog is $CODEX_HOME/skills/<name>/SKILL.md (repo uses .agents/skills/).
230
- // Refresh home skills when a Codex home exists. Project MCP is installed above in
231
- // .codex/config.toml. Non-fatal: a permission error (e.g. sandbox) shouldn't fail upgrade.
232
- const codexHomeBase = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
233
- if (fs.existsSync(codexHomeBase)) {
234
- console.log(`\n Refreshing Codex home (${codexHomeBase})…`);
235
- runArkCheck(
236
- ['--root', root, '--install-agent-gates', '--skills-only', '--codex-home', '--force'],
237
- { cwd: root }
238
- );
239
- }
240
-
241
- console.log('\n3/4 Migrating command runners to this project’s package manager…');
242
- status = runArkCheck(['--root', root, '--install-agent-gates', '--migrate-commands'], { cwd: root });
243
- if (status !== 0) return status;
244
-
245
- if (!args.strict) {
246
- console.log('\n4/4 Skipping the strict check (--no-strict). Upgrade complete.');
247
- return 0;
248
- }
249
- console.log('\n4/4 Verifying architecture…');
250
- return runArkCheck(
251
- ['--root', root, '--config', 'ark.config.json', '--strict-merge'],
252
- { cwd: root }
253
- );
254
- }
255
-
256
212
  function runArkCheck(args, options = {}) {
257
213
  const result = spawnSync(process.execPath, [arkCheck, ...args], {
258
214
  cwd: options.cwd,
@@ -790,9 +746,12 @@ async function main() {
790
746
  }
791
747
  if (
792
748
  args.command !== 'preflight' &&
793
- (args.changes || args.changeMap || args.config !== 'ark.config.json')
749
+ (args.changes || args.changeMap || args.manifest || args.tsconfig ||
750
+ args.config !== 'ark.config.json')
794
751
  ) {
795
- console.error('--changes, --change-map, and --config are supported by ark preflight.');
752
+ console.error(
753
+ '--changes, --change-map, --config, --manifest, and --tsconfig are supported by ark preflight.'
754
+ );
796
755
  return 2;
797
756
  }
798
757
  const enforcement = validateHardWriteRequest({
@@ -830,7 +789,7 @@ async function main() {
830
789
 
831
790
  if (args.command === 'upgrade' || args.command === 'update') {
832
791
  try {
833
- return await upgrade(args);
792
+ return runUpgradeCommand(args, { arkCheck, packageInstallArgv, runArkCheck });
834
793
  } catch (error) {
835
794
  console.error(error instanceof Error ? error.message : String(error));
836
795
  return 2;
@@ -848,11 +807,19 @@ async function main() {
848
807
  configPath
849
808
  ).config;
850
809
  const changeMap = args.changeMap ? readChangeMapFile(args.root, args.changeMap) : undefined;
810
+ const manifestPath = args.manifest
811
+ ? path.resolve(args.root, args.manifest)
812
+ : undefined;
813
+ const manifest = manifestPath ? JSON.parse(fs.readFileSync(manifestPath, 'utf8')) : undefined;
814
+ const loadedTypeScript = await loadTypeScript(args.root);
851
815
  const result = prepareChangeFromRoot({
852
816
  root: args.root,
853
817
  config,
854
818
  configSource: configPath,
855
819
  changes: readChangeSetFile(args.root, args.changes),
820
+ ts: loadedTypeScript.ts ?? undefined,
821
+ tsconfig: args.tsconfig,
822
+ manifest,
856
823
  ...(changeMap ? { changeMap: changeMap.input, changeMapSource: changeMap.source } : {}),
857
824
  });
858
825
  if (args.json) console.log(JSON.stringify(result, null, 2));
@@ -8,7 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/adapter-contract.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.1';
11
+ export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.3';
12
12
  function text(value) {
13
13
  return typeof value === 'string' && value.length > 0 ? value : undefined;
14
14
  }
@@ -50,6 +50,23 @@ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
50
50
  ...(text(violation.fromLayer) ? { fromLayer: text(violation.fromLayer) } : {}),
51
51
  ...(text(violation.toLayer) ? { toLayer: text(violation.toLayer) } : {}),
52
52
  ...(typeof violation.typeOnly === 'boolean' ? { typeOnly: violation.typeOnly } : {}),
53
+ ...(typeof violation.targetTypeOnlyExports === 'boolean'
54
+ ? { targetTypeOnlyExports: violation.targetTypeOnlyExports }
55
+ : {}),
56
+ ...(typeof violation.sourcePureTypeModule === 'boolean'
57
+ ? { sourcePureTypeModule: violation.sourcePureTypeModule }
58
+ : {}),
59
+ ...(typeof violation.namedBindingsTypeOnly === 'boolean'
60
+ ? { namedBindingsTypeOnly: violation.namedBindingsTypeOnly }
61
+ : {}),
62
+ ...(typeof violation.portProofEligible === 'boolean'
63
+ ? { portProofEligible: violation.portProofEligible }
64
+ : {}),
65
+ ...(typeof violation.peerIsolation === 'boolean'
66
+ ? { peerIsolation: violation.peerIsolation }
67
+ : {}),
68
+ ...(text(violation.capability) ? { capability: text(violation.capability) } : {}),
69
+ ...(text(violation.edgeKind) ? { edgeKind: text(violation.edgeKind) } : {}),
53
70
  };
54
71
  return {
55
72
  ruleId,
@@ -65,25 +82,153 @@ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
65
82
  };
66
83
  }
67
84
  export function createAdapterResult(input) {
68
- return {
85
+ const completeness = input.completeness ?? 'complete';
86
+ const mode = input.mode ?? 'lexical-compatibility';
87
+ if (completeness === 'complete' && (input.completenessReasons?.length ?? 0) > 0) {
88
+ throw new Error('completenessReasons must be empty when completeness is complete.');
89
+ }
90
+ const completenessReasons = completeness === 'complete'
91
+ ? []
92
+ : input.completenessReasons && input.completenessReasons.length > 0
93
+ ? input.completenessReasons.map((reason) => ({
94
+ code: text(reason.code) ?? 'ANALYSIS_EVIDENCE_INCOMPLETE',
95
+ message: text(reason.message) ??
96
+ `Analysis ${completeness}: required evidence is incomplete.`,
97
+ ...(text(reason.file) ? { file: text(reason.file) } : {}),
98
+ }))
99
+ : [
100
+ {
101
+ code: completeness === 'unavailable'
102
+ ? 'ANALYSIS_UNAVAILABLE'
103
+ : 'ANALYSIS_EVIDENCE_INCOMPLETE',
104
+ message: `Analysis ${completeness}: required evidence is incomplete.`,
105
+ },
106
+ ];
107
+ const evidence = {
108
+ ...(text(input.policyHash) ? { policyHash: text(input.policyHash) } : {}),
109
+ ...(text(input.resolverIdentity) ? { resolverIdentity: text(input.resolverIdentity) } : {}),
110
+ ...(text(input.factsHash) ? { factsHash: text(input.factsHash) } : {}),
111
+ ...(text(input.candidateTreeHash) ? { candidateTreeHash: text(input.candidateTreeHash) } : {}),
112
+ };
113
+ if (mode === 'resolved-candidate-facts' && completeness !== 'unavailable') {
114
+ for (const field of ['policyHash', 'resolverIdentity', 'factsHash', 'candidateTreeHash']) {
115
+ if (!evidence[field]) {
116
+ throw new Error(`${field} is required for resolved ${completeness} adapter evidence.`);
117
+ }
118
+ }
119
+ }
120
+ const diagnostics = [
121
+ ...(input.violations ?? []).map((item) => toAdapterDiagnostic(item, 'error')),
122
+ ...(input.warnings ?? []).map((item) => toAdapterDiagnostic(item, 'warning')),
123
+ ];
124
+ const base = {
69
125
  schemaVersion: ARK_ANALYSIS_RESULT_SCHEMA_VERSION,
70
- valid: input.valid,
71
- diagnostics: [
72
- ...(input.violations ?? []).map((item) => toAdapterDiagnostic(item, 'error')),
73
- ...(input.warnings ?? []).map((item) => toAdapterDiagnostic(item, 'warning')),
74
- ],
126
+ completenessReasons,
127
+ diagnostics,
75
128
  };
129
+ if (mode === 'resolved-candidate-facts') {
130
+ if (completeness === 'unavailable') {
131
+ return {
132
+ ...base,
133
+ mode,
134
+ valid: false,
135
+ completeness,
136
+ ...evidence,
137
+ };
138
+ }
139
+ const resolvedEvidence = {
140
+ policyHash: evidence.policyHash,
141
+ resolverIdentity: evidence.resolverIdentity,
142
+ factsHash: evidence.factsHash,
143
+ candidateTreeHash: evidence.candidateTreeHash,
144
+ };
145
+ if (completeness === 'complete') {
146
+ return {
147
+ ...base,
148
+ mode,
149
+ valid: input.valid,
150
+ completeness,
151
+ ...resolvedEvidence,
152
+ };
153
+ }
154
+ return {
155
+ ...base,
156
+ mode,
157
+ valid: false,
158
+ completeness,
159
+ ...resolvedEvidence,
160
+ };
161
+ }
162
+ if (completeness === 'complete') {
163
+ return { ...base, mode, valid: input.valid, completeness, ...evidence };
164
+ }
165
+ return { ...base, mode, valid: false, completeness, ...evidence };
76
166
  }
77
167
  export const ARK_ANALYSIS_RESULT_SCHEMA = {
78
168
  $schema: 'https://json-schema.org/draft/2020-12/schema',
79
- $id: 'https://unpkg.com/arkgate@2/schemas/ark.analysis-result.schema.json',
169
+ $id: 'https://unpkg.com/arkgate@3/schemas/ark.analysis-result.schema.json',
80
170
  title: 'ArkGate analysis result',
81
171
  type: 'object',
82
172
  additionalProperties: false,
83
- required: ['schemaVersion', 'valid', 'diagnostics'],
173
+ required: [
174
+ 'schemaVersion',
175
+ 'mode',
176
+ 'valid',
177
+ 'completeness',
178
+ 'completenessReasons',
179
+ 'diagnostics',
180
+ ],
181
+ allOf: [
182
+ {
183
+ if: {
184
+ properties: { completeness: { enum: ['partial', 'unavailable'] } },
185
+ required: ['completeness'],
186
+ },
187
+ then: { properties: { valid: { const: false } } },
188
+ },
189
+ {
190
+ if: {
191
+ properties: {
192
+ mode: { const: 'resolved-candidate-facts' },
193
+ completeness: { enum: ['complete', 'partial'] },
194
+ },
195
+ required: ['mode', 'completeness'],
196
+ },
197
+ then: {
198
+ required: ['policyHash', 'resolverIdentity', 'factsHash', 'candidateTreeHash'],
199
+ },
200
+ },
201
+ {
202
+ if: {
203
+ properties: { completeness: { const: 'complete' } },
204
+ required: ['completeness'],
205
+ },
206
+ then: { properties: { completenessReasons: { maxItems: 0 } } },
207
+ else: { properties: { completenessReasons: { minItems: 1 } } },
208
+ },
209
+ ],
84
210
  properties: {
85
211
  schemaVersion: { const: ARK_ANALYSIS_RESULT_SCHEMA_VERSION },
212
+ mode: { enum: ['lexical-compatibility', 'resolved-candidate-facts'] },
86
213
  valid: { type: 'boolean' },
214
+ completeness: { enum: ['complete', 'partial', 'unavailable'] },
215
+ completenessReasons: {
216
+ type: 'array',
217
+ items: {
218
+ type: 'object',
219
+ additionalProperties: false,
220
+ required: ['code', 'message'],
221
+ properties: {
222
+ code: { type: 'string', minLength: 1 },
223
+ message: { type: 'string', minLength: 1 },
224
+ file: { type: 'string', minLength: 1 },
225
+ },
226
+ },
227
+ },
228
+ policyHash: { type: 'string', minLength: 1 },
229
+ resolverIdentity: { type: 'string', minLength: 1 },
230
+ factsHash: { type: 'string', minLength: 1 },
231
+ candidateTreeHash: { type: 'string', minLength: 1 },
87
232
  diagnostics: {
88
233
  type: 'array',
89
234
  items: {
@@ -112,6 +257,13 @@ export const ARK_ANALYSIS_RESULT_SCHEMA = {
112
257
  fromLayer: { type: 'string' },
113
258
  toLayer: { type: 'string' },
114
259
  typeOnly: { type: 'boolean' },
260
+ targetTypeOnlyExports: { type: 'boolean' },
261
+ sourcePureTypeModule: { type: 'boolean' },
262
+ namedBindingsTypeOnly: { type: 'boolean' },
263
+ portProofEligible: { type: 'boolean' },
264
+ peerIsolation: { type: 'boolean' },
265
+ capability: { type: 'string', minLength: 1 },
266
+ edgeKind: { type: 'string', minLength: 1 },
115
267
  },
116
268
  },
117
269
  nextAction: { type: 'string', minLength: 1 },
@@ -120,6 +120,7 @@ export {
120
120
  } from './weakest-link.mjs';
121
121
 
122
122
  export {
123
+ buildManagedAssetCatalog,
123
124
  staleRunnerGateFiles,
124
125
  warnLockfileConflict,
125
126
  runMigrateCommands,
@@ -0,0 +1,28 @@
1
+ /** Shared fail-closed completeness semantics for CLI/MCP Tooling surfaces. */
2
+
3
+ export const ANALYSIS_COMPLETENESS = Object.freeze({
4
+ complete: 'complete',
5
+ partial: 'partial',
6
+ unavailable: 'unavailable',
7
+ });
8
+
9
+ export function normalizeAnalysisCompleteness(value) {
10
+ return value === ANALYSIS_COMPLETENESS.complete ||
11
+ value === ANALYSIS_COMPLETENESS.partial ||
12
+ value === ANALYSIS_COMPLETENESS.unavailable
13
+ ? value
14
+ : ANALYSIS_COMPLETENESS.unavailable;
15
+ }
16
+
17
+ export function completenessFromParseHealth(parseHealth) {
18
+ if (parseHealth?.available !== true) return ANALYSIS_COMPLETENESS.unavailable;
19
+ return parseHealth.affectedFiles > 0
20
+ ? ANALYSIS_COMPLETENESS.partial
21
+ : ANALYSIS_COMPLETENESS.complete;
22
+ }
23
+
24
+ export function analysisIncompleteStatement(completeness) {
25
+ return completeness === ANALYSIS_COMPLETENESS.partial
26
+ ? 'Analysis incomplete: governed parse diagnostics prevent a complete architecture verdict.'
27
+ : 'Analysis unavailable: no API-compatible TypeScript host could produce architecture evidence.';
28
+ }