arkgate 4.8.22 → 4.8.24

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 (136) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +27 -78
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +1117 -38
  6. package/bin/ark-mcp-runtime.mjs +412 -138
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +39 -0
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/agent-projection-command.mjs +5 -1
  13. package/bin/lib/analysis-engine.mjs +15 -11
  14. package/bin/lib/architecture-scan.mjs +50 -6
  15. package/bin/lib/ark-order-doctor.mjs +1 -1
  16. package/bin/lib/ark-order-facts.mjs +410 -15
  17. package/bin/lib/ark-order-invariants.mjs +51 -9
  18. package/bin/lib/ark-order-sensors.mjs +28 -101
  19. package/bin/lib/ark-order-types.mjs +2 -0
  20. package/bin/lib/ark-run-doctor.mjs +14 -0
  21. package/bin/lib/ark-run-facts.mjs +434 -11
  22. package/bin/lib/ark-run-sensors.mjs +5 -3
  23. package/bin/lib/arkgate-pins.mjs +91 -0
  24. package/bin/lib/arkrules-sensors.mjs +50 -249
  25. package/bin/lib/check-args.mjs +23 -1
  26. package/bin/lib/ci-and-commands.mjs +16 -6
  27. package/bin/lib/class-source-scan.mjs +479 -0
  28. package/bin/lib/codex-home.mjs +4 -2
  29. package/bin/lib/config-contract-slices.mjs +701 -0
  30. package/bin/lib/config-contract.mjs +5 -2
  31. package/bin/lib/config-warnings.mjs +32 -0
  32. package/bin/lib/dashboard-cli.mjs +62 -0
  33. package/bin/lib/design-delta.mjs +1 -1
  34. package/bin/lib/diagnostic-catalog.mjs +45 -6
  35. package/bin/lib/doctor-advisories.mjs +84 -1
  36. package/bin/lib/doctor-next-actions.mjs +17 -2
  37. package/bin/lib/doctor-plan.mjs +7 -9
  38. package/bin/lib/effective-contract-load.mjs +121 -59
  39. package/bin/lib/enforcement-honesty.mjs +22 -0
  40. package/bin/lib/enforcement-profiles.mjs +40 -4
  41. package/bin/lib/first-run-help.mjs +3 -2
  42. package/bin/lib/flat-parent-importers.mjs +137 -0
  43. package/bin/lib/flat-parent-pilot.mjs +192 -0
  44. package/bin/lib/gate-files.mjs +29 -2
  45. package/bin/lib/git-change-scope.mjs +109 -0
  46. package/bin/lib/github-enforcement.mjs +1 -1
  47. package/bin/lib/hook-templates.mjs +18 -16
  48. package/bin/lib/host-support-matrix.mjs +3 -1
  49. package/bin/lib/html-report-advisories.mjs +2 -0
  50. package/bin/lib/html-report-depth.mjs +3 -1
  51. package/bin/lib/import-resolve.mjs +13 -1
  52. package/bin/lib/install-migrate.mjs +50 -73
  53. package/bin/lib/invariant-coverage.mjs +59 -11
  54. package/bin/lib/mcp-adoption.mjs +9 -1
  55. package/bin/lib/mcp-hook-payload.mjs +401 -61
  56. package/bin/lib/package-manager.mjs +59 -1
  57. package/bin/lib/passthrough-commands.mjs +83 -0
  58. package/bin/lib/persistence-write-hint.mjs +81 -0
  59. package/bin/lib/physical-cohesion.mjs +16 -18
  60. package/bin/lib/pilot-loop.mjs +21 -8
  61. package/bin/lib/pin-dependent-gates.mjs +117 -0
  62. package/bin/lib/policy-delta-io.mjs +168 -10
  63. package/bin/lib/prepare-change.mjs +120 -6
  64. package/bin/lib/prepare-write.mjs +1 -0
  65. package/bin/lib/presets.mjs +3 -5
  66. package/bin/lib/remediation.mjs +23 -4
  67. package/bin/lib/resolved-candidate-facts.mjs +315 -55
  68. package/bin/lib/rules-inventory.mjs +22 -3
  69. package/bin/lib/rules-under-contract.mjs +179 -5
  70. package/bin/lib/scan-files.mjs +32 -0
  71. package/bin/lib/snippet-analysis.mjs +140 -6
  72. package/bin/lib/source-policy.mjs +34 -0
  73. package/bin/lib/stable-hash.mjs +90 -0
  74. package/bin/lib/start-install-recovery.mjs +71 -1
  75. package/bin/lib/start-preview.mjs +30 -0
  76. package/bin/lib/status-command.mjs +33 -4
  77. package/bin/lib/status-manifest.mjs +32 -4
  78. package/bin/lib/team-parliament-io.mjs +79 -75
  79. package/bin/lib/team-parliament.mjs +6 -1
  80. package/bin/lib/write-path-capabilities.mjs +4 -2
  81. package/dist/chunk-A5EIY3HI.cjs +5 -0
  82. package/dist/chunk-MBXCFLO4.js +16 -0
  83. package/dist/chunk-RNQHRDCQ.cjs +16 -0
  84. package/dist/chunk-SVSQVGFJ.js +5 -0
  85. package/dist/{configTypes-CTELo87-.d.ts → configTypes-BJbaeoj4.d.cts} +53 -4
  86. package/dist/configTypes-BJbaeoj4.d.ts +246 -0
  87. package/dist/eslint/index.cjs +17 -4
  88. package/dist/eslint/index.d.cts +359 -0
  89. package/dist/eslint/index.d.ts +178 -158
  90. package/dist/eslint/index.js +10 -4
  91. package/dist/index.cjs +38 -37
  92. package/dist/index.d.cts +5475 -0
  93. package/dist/index.d.ts +3408 -296
  94. package/dist/index.js +38 -37
  95. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  96. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  97. package/dist/nestjs/index.cjs +1 -18
  98. package/dist/nestjs/index.d.cts +22 -0
  99. package/dist/nestjs/index.d.ts +1 -3
  100. package/dist/nestjs/index.js +1 -18
  101. package/dist/order/index.cjs +1 -1
  102. package/dist/order/index.d.cts +155 -0
  103. package/dist/order/index.d.ts +15 -3
  104. package/dist/order/index.js +1 -1
  105. package/dist/runtime/index.cjs +13 -30
  106. package/dist/{diagnosticCatalog-WD2TPSwZ.d.ts → runtime/index.d.cts} +814 -290
  107. package/dist/runtime/index.d.ts +2678 -108
  108. package/dist/runtime/index.js +13 -30
  109. package/dist/{types-yDNEQ603.d.ts → types-DRinG-vT.d.cts} +576 -140
  110. package/dist/types-DRinG-vT.d.ts +1792 -0
  111. package/docs/README.md +3 -1
  112. package/docs/agent-guide.md +92 -15
  113. package/docs/ai-gates.md +160 -26
  114. package/docs/arkorder.md +32 -15
  115. package/docs/brownfield-adoption.md +5 -1
  116. package/docs/configuration.md +273 -12
  117. package/docs/develop.md +2 -1
  118. package/docs/diagnostics.md +122 -13
  119. package/docs/package-surface.md +31 -8
  120. package/docs/product-voice.md +1 -1
  121. package/package.json +59 -18
  122. package/schemas/ark.analysis-result.schema.json +15 -0
  123. package/schemas/ark.config.schema.json +163 -4
  124. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  125. package/schemas/ark.status-manifest.schema.json +22 -0
  126. package/server.json +3 -9
  127. package/templates/agent-skills/README.md +1 -1
  128. package/templates/agent-skills/ark-adopt/SKILL.md +4 -1
  129. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  130. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  131. package/templates/arkrules/PersistenceAdapters.json +1 -1
  132. package/templates/arkrules/PresentationAdapters.json +1 -1
  133. package/templates/skills/ark-adopt.md +4 -1
  134. package/templates/skills/ark-order.md +4 -2
  135. package/templates/skills/ark-runtime.md +6 -1
  136. package/dist/types-fQ9-KrBj.d.ts +0 -183
@@ -3,17 +3,31 @@ import path from 'node:path';
3
3
  import {
4
4
  loadArchitectureChangeMap,
5
5
  loadContract,
6
- preflightResolvedChange,
6
+ preflightTrustedResolvedChange,
7
7
  } from './analysis-engine.mjs';
8
8
  import { createAdapterResult } from './adapter-contract.mjs';
9
9
  import { isGovernableSourceFile } from './scan-files.mjs';
10
10
  import {
11
11
  canonicalizeCandidateChanges,
12
+ createResolverIngestCache,
12
13
  resolveCandidateFacts,
13
14
  } from './resolved-candidate-facts.mjs';
14
15
  import { effectiveAnalysisConfig } from './analysis-policy.mjs';
15
16
  import { isScanExcludedRelative } from '../ark-shared.mjs';
16
17
  import { classifyChangeSet, evaluateTeamGate } from './team-parliament.mjs';
18
+ import { loadEffectiveArkRulesFromDisk } from './effective-contract-load.mjs';
19
+ import {
20
+ coverageOptionsFromConfig,
21
+ invariantIdsFromCatalog,
22
+ loadInvariantCoverageInputs,
23
+ } from './invariant-coverage-io.mjs';
24
+ import { catalogHasEnforcedInvariant } from './invariant-coverage.mjs';
25
+ import {
26
+ declaredCoverageRootsPresent,
27
+ declaredInvariantTestsPathPresent,
28
+ scanDemandsInvariantTestsPath,
29
+ } from './invariant-tests-path.mjs';
30
+ import { loadArkRuleFileHints } from './arkrule-file-hints.mjs';
17
31
 
18
32
  function candidatePath(value) {
19
33
  if (typeof value !== 'string' || value.trim() === '') {
@@ -88,6 +102,73 @@ export function normalizeChangeSet(input) {
88
102
  });
89
103
  }
90
104
 
105
+ function overlayContents(contents, overlay) {
106
+ const out = { ...(contents ?? {}) };
107
+ for (const change of overlay) {
108
+ if (change.delete === true) delete out[change.path];
109
+ else out[change.path] = change.content;
110
+ }
111
+ return out;
112
+ }
113
+
114
+ /**
115
+ * ArkRules engine inputs for base (disk) and candidate (disk + in-memory overlay):
116
+ * invariant coverage contents and structural file hints, loaded exactly the way
117
+ * architecture-scan loads them for ark-check. Undefined when ArkRules is off.
118
+ */
119
+ function arkRulesAnalysisInputs({ root, config, arkRules, baseFacts, candidateFacts, overlay }) {
120
+ const structure = arkRules?.structure ?? [];
121
+ const invariants = arkRules?.invariants ?? [];
122
+ if (structure.length === 0 && invariants.length === 0) return {};
123
+ const adopted = scanDemandsInvariantTestsPath(root, {});
124
+ const shared = {
125
+ ...(adopted ? { adopted: true } : {}),
126
+ ...(adopted && declaredInvariantTestsPathPresent(root, config.coverage) === false
127
+ ? { invariantTestsPathPresent: false }
128
+ : {}),
129
+ ...(catalogHasEnforcedInvariant(invariants) &&
130
+ declaredCoverageRootsPresent(root, config.coverage) === false
131
+ ? { coverageRootsPresent: false }
132
+ : {}),
133
+ };
134
+ const baseCoverage =
135
+ invariants.length > 0
136
+ ? loadInvariantCoverageInputs(root, baseFacts, {
137
+ invariantIds: invariantIdsFromCatalog(arkRules),
138
+ ...coverageOptionsFromConfig(config),
139
+ })
140
+ : undefined;
141
+ const candidateCoverage = baseCoverage
142
+ ? {
143
+ ...baseCoverage,
144
+ fileContents: overlayContents(baseCoverage.fileContents, overlay),
145
+ testFiles: (baseCoverage.testFiles ?? []).filter(
146
+ (file) => !overlay.some((change) => change.delete === true && change.path === file)
147
+ ),
148
+ }
149
+ : undefined;
150
+ const baseHints = loadArkRuleFileHints(root, baseFacts, arkRules, baseCoverage?.fileContents);
151
+ // Candidate hints must read the overlay, never the stale on-disk text of a changed file.
152
+ const candidateHints = loadArkRuleFileHints(
153
+ root,
154
+ candidateFacts,
155
+ arkRules,
156
+ overlayContents(candidateCoverage?.fileContents ?? {}, overlay)
157
+ );
158
+ return {
159
+ baseAnalysisInputs: {
160
+ ...shared,
161
+ ...(baseCoverage ? { coverageInputs: baseCoverage } : {}),
162
+ ...(baseHints ? { fileHints: baseHints } : {}),
163
+ },
164
+ candidateAnalysisInputs: {
165
+ ...shared,
166
+ ...(candidateCoverage ? { coverageInputs: candidateCoverage } : {}),
167
+ ...(candidateHints ? { fileHints: candidateHints } : {}),
168
+ },
169
+ };
170
+ }
171
+
91
172
  export function prepareChangeFromRoot({
92
173
  root,
93
174
  config,
@@ -115,10 +196,22 @@ export function prepareChangeFromRoot({
115
196
  for (const change of normalizedChanges) {
116
197
  assertInsideProject(root, change.path);
117
198
  }
118
- const contract = loadContract(
119
- effectiveConfig,
120
- configSource ?? path.join(root, 'ark.config.json')
121
- );
199
+ const contractSource = configSource ?? path.join(root, 'ark.config.json');
200
+ // Same Effective Contract as ark-check: ArkRules ride policyHash and the verdict,
201
+ // and a broken reference fails closed instead of silently dropping the catalog.
202
+ const arkRulesLoad = loadEffectiveArkRulesFromDisk(root, effectiveConfig);
203
+ if (arkRulesLoad.errors.length > 0) {
204
+ const message = arkRulesLoad.errors
205
+ .map((issue) => (typeof issue === 'string' ? issue : `- ${issue.path}: ${issue.message}`))
206
+ .join('\n');
207
+ const error = new Error(`Invalid Effective Contract (${contractSource}):\n${message}`);
208
+ error.code = 'ARKRULES_LOAD_FAILED';
209
+ error.issues = arkRulesLoad.errors;
210
+ throw error;
211
+ }
212
+ const contract = loadContract(effectiveConfig, contractSource, {
213
+ arkRules: arkRulesLoad.arkRules,
214
+ });
122
215
  const loadedChangeMap =
123
216
  changeMap === undefined
124
217
  ? undefined
@@ -129,24 +222,45 @@ export function prepareChangeFromRoot({
129
222
  changes: normalizedChanges,
130
223
  });
131
224
  for (const change of canonicalChanges) assertGovernedSource(effectiveConfig, change.path);
225
+ // Files the overlay does not touch are parsed once for both trees.
226
+ const ingestCache = createResolverIngestCache(contract.config);
132
227
  const baseFacts = resolveCandidateFacts({
133
228
  root,
134
229
  config: contract.config,
135
230
  ts,
231
+ ingestCache,
136
232
  ...(tsconfig ? { tsconfig } : {}),
137
233
  });
138
234
  const candidateFacts = resolveCandidateFacts({
139
235
  root,
140
236
  config: contract.config,
141
237
  ts,
238
+ ingestCache,
142
239
  changes: normalizedOverlayChanges,
143
240
  ...(tsconfig ? { tsconfig } : {}),
144
241
  });
145
- const result = preflightResolvedChange({
242
+ ingestCache.records.clear();
243
+ const { baseAnalysisInputs, candidateAnalysisInputs } = arkRulesAnalysisInputs({
244
+ root,
245
+ config: effectiveConfig,
246
+ arkRules: arkRulesLoad.arkRules,
247
+ baseFacts,
248
+ candidateFacts,
249
+ overlay: canonicalizeCandidateChanges({
250
+ root,
251
+ config: contract.config,
252
+ changes: normalizedOverlayChanges,
253
+ }),
254
+ });
255
+ // Both fact sets were created (validated, frozen) by this bundle instance just
256
+ // above: skip re-validating two whole-project copies of each.
257
+ const result = preflightTrustedResolvedChange({
146
258
  contract,
147
259
  baseFacts,
148
260
  candidateFacts,
149
261
  changes: canonicalChanges,
262
+ ...(baseAnalysisInputs ? { baseAnalysisInputs } : {}),
263
+ ...(candidateAnalysisInputs ? { candidateAnalysisInputs } : {}),
150
264
  ...(loadedChangeMap ? { changeMap: loadedChangeMap } : {}),
151
265
  });
152
266
  const completeness =
@@ -41,6 +41,7 @@ export function buildJudgmentBrief(violations) {
41
41
  targetTypeOnlyExports: v.targetTypeOnlyExports,
42
42
  namedBindingsTypeOnly: v.namedBindingsTypeOnly,
43
43
  peerIsolation: v.peerIsolation ?? v.details?.peerIsolation,
44
+ reasonId: v.reasonId ?? v.details?.reasonId,
44
45
  edgeKind: v.edgeKind ?? v.details?.importKind,
45
46
  fromLayer: v.fromLayer,
46
47
  toLayer: v.toLayer,
@@ -6,6 +6,7 @@ import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import {
8
8
  applyFrameworkLayoutOverlays,
9
+ cloneArchetypeForLayer,
9
10
  createElevenLayerConfig,
10
11
  DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
11
12
  discoverRepoUnits,
@@ -371,11 +372,8 @@ export function buildArkRulesTemplateForLayer(layerName, role = resolveLayerSens
371
372
  const sourcePath = path.join(ARKRULES_TEMPLATES_DIR, archetype);
372
373
  if (fs.existsSync(sourcePath)) {
373
374
  try {
374
- const parsed = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
375
- return {
376
- ...parsed,
377
- layer: layerName,
378
- };
375
+ // Unique ids across arkrules/*.json so `--promote <id> --apply` works on the starter.
376
+ return cloneArchetypeForLayer(JSON.parse(fs.readFileSync(sourcePath, 'utf8')), layerName);
379
377
  } catch {
380
378
  // Fall through to generic mold.
381
379
  }
@@ -8,6 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/remediation.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
+ import { sliceReasonHint } from './diagnostic-catalog.mjs';
11
12
  export const REMEDIATION_CLASSES = [
12
13
  'mechanical-safe',
13
14
  'judgment',
@@ -67,7 +68,15 @@ export function classifyLayerImportKind(target, extra) {
67
68
  return 'unknown';
68
69
  return 'unknown';
69
70
  }
71
+ /** The reason's fix, plus its advisory note only when this finding is a warning. */
72
+ function sliceHintAction(hint, violation) {
73
+ return violation.failsStrict === false && hint.advisoryNote ? `${hint.fix} ${hint.advisoryNote}` : hint.fix;
74
+ }
70
75
  export function layerImportNextAction(violation) {
76
+ // A nested-wall reason has its own fix. It wins over the generic peer text.
77
+ const hint = sliceReasonHint(typeof violation.reasonId === 'string' ? violation.reasonId : undefined);
78
+ if (hint)
79
+ return `${sliceHintAction(hint, violation)} Then preflight again.`;
71
80
  if (violation.typeOnly || violation.targetTypeOnlyExports || violation.namedBindingsTypeOnly) {
72
81
  return 'Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.';
73
82
  }
@@ -198,6 +207,11 @@ export function deterministicNextAction(violation) {
198
207
  switch (violation.ruleId) {
199
208
  case 'LAYER_IMPORT_VIOLATION':
200
209
  return layerImportNextAction(violation);
210
+ case 'CONFIG_CHILD_SLICES_VERSION':
211
+ // The finding already names the pinned file and the version to bump to.
212
+ return typeof violation.nextAction === 'string' && violation.nextAction.length > 0
213
+ ? violation.nextAction
214
+ : 'Bump the pinned arkgate named in the finding to the version it gives, then run Ark again.';
201
215
  case 'FORBIDDEN_GLOBAL':
202
216
  return `Inject ${violation.target ?? 'the capability'} through a port, test at the public interface, then preflight again.`;
203
217
  case 'CAPABILITY_VIOLATION':
@@ -423,11 +437,13 @@ export function enrichViolationWithFixClass(violation) {
423
437
  ? 'The imported module only exports types — use `import type` and place the type in a layer both sides may share.'
424
438
  : 'This is a type-only import — move the type to a layer both sides may share, or relocate the file to match its role.';
425
439
  }
426
- else if (violation.peerIsolation) {
440
+ else if (violation.peerIsolation || sliceReasonHint(violation.reasonId)) {
441
+ const hint = sliceReasonHint(violation.reasonId);
427
442
  enriched.fixClass = 'cross-slice-boundary';
428
443
  enriched.effort = 'medium';
429
- enriched.enthusiastHint =
430
- 'Cross-slice import blocked (peerIsolation). Do not import another feature/context directly — extract shared code to a shared layer, or coordinate via events/ports. Moving code across slices is a judgment call, not a mechanical auto-fix.';
444
+ enriched.enthusiastHint = hint
445
+ ? `${hint.why} ${sliceHintAction(hint, violation)}`
446
+ : 'Cross-slice import blocked (peerIsolation). Do not import another feature/context directly — extract shared code to a shared layer, or coordinate via events/ports. Moving code across slices is a judgment call, not a mechanical auto-fix.';
431
447
  }
432
448
  else {
433
449
  const kind = classifyLayerImportKind(typeof violation.target === 'string' ? violation.target : '', {
@@ -539,6 +555,7 @@ export function enrichViolationWithFixClass(violation) {
539
555
  case 'ARKORDER_XI_TTL':
540
556
  case 'ARKORDER_STALE_SIGMA':
541
557
  case 'ARKORDER_UNVALVED_RELEASE':
558
+ case 'ARKORDER_STALE_PROPOSAL':
542
559
  enriched.fixClass = 'arkorder-usage';
543
560
  enriched.effort = 'medium';
544
561
  enriched.enthusiastHint =
@@ -560,7 +577,9 @@ export function enrichViolationWithFixClass(violation) {
560
577
  ? 'Refresh σ. ξ does not expire.'
561
578
  : violation.ruleId === 'ARKORDER_UNVALVED_RELEASE'
562
579
  ? 'The choice is already frozen. proposeRelease then apply — do not call release() again with a different value.'
563
- : 'Call createOrderPlane from arkgate/order in a listed plane root so the app actually freezes a pattern.';
580
+ : violation.ruleId === 'ARKORDER_STALE_PROPOSAL'
581
+ ? 'That proposal was computed against an older pattern. Propose again, review the new blast radius, then apply.'
582
+ : 'Call createOrderPlane from arkgate/order in a listed plane root so the app actually freezes a pattern.';
564
583
  break;
565
584
  default:
566
585
  enriched.fixClass = 'review-contract';