arkgate 4.8.23 → 4.8.25

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 (201) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +41 -91
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +707 -121
  6. package/bin/ark-mcp-runtime.mjs +448 -169
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +42 -22
  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/adoption-stance.mjs +2 -2
  13. package/bin/lib/adr-path.mjs +3 -3
  14. package/bin/lib/agent-gates.mjs +0 -61
  15. package/bin/lib/agent-homes.mjs +12 -21
  16. package/bin/lib/agent-projection-command.mjs +9 -5
  17. package/bin/lib/ambient-state.mjs +9 -30
  18. package/bin/lib/analysis-engine.mjs +15 -11
  19. package/bin/lib/architecture-scan.mjs +61 -15
  20. package/bin/lib/ark-order-doctor.mjs +4 -29
  21. package/bin/lib/ark-order-facts.mjs +319 -17
  22. package/bin/lib/ark-order-sensors.mjs +28 -101
  23. package/bin/lib/ark-order-types.mjs +3 -2
  24. package/bin/lib/ark-run-doctor.mjs +17 -28
  25. package/bin/lib/ark-run-facts.mjs +461 -48
  26. package/bin/lib/ark-run-sensors.mjs +6 -4
  27. package/bin/lib/arkgate-pins.mjs +91 -0
  28. package/bin/lib/arkrule-file-hints.mjs +2 -47
  29. package/bin/lib/arkrules-contract.mjs +124 -133
  30. package/bin/lib/arkrules-sensors.mjs +55 -252
  31. package/bin/lib/check-args.mjs +85 -1
  32. package/bin/lib/ci-and-commands.mjs +24 -78
  33. package/bin/lib/ci-merge-boundary.mjs +1 -1
  34. package/bin/lib/class-source-scan.mjs +479 -0
  35. package/bin/lib/clone-detection.mjs +474 -0
  36. package/bin/lib/codex-home.mjs +5 -3
  37. package/bin/lib/config-contract-slices.mjs +739 -0
  38. package/bin/lib/config-contract.mjs +42 -607
  39. package/bin/lib/config-extras.mjs +16 -22
  40. package/bin/lib/config-warnings.mjs +32 -0
  41. package/bin/lib/contract-smells.mjs +33 -15
  42. package/bin/lib/core-ratchet.mjs +0 -2
  43. package/bin/lib/dashboard-cli.mjs +62 -0
  44. package/bin/lib/deep-module-coach.mjs +2 -4
  45. package/bin/lib/deploy-path.mjs +13 -38
  46. package/bin/lib/design-delta.mjs +1 -1
  47. package/bin/lib/design-smells.mjs +2 -2
  48. package/bin/lib/diagnostic-catalog.mjs +23 -9
  49. package/bin/lib/doctor-advisories.mjs +78 -8
  50. package/bin/lib/doctor-green-cite.mjs +1 -1
  51. package/bin/lib/doctor-human.mjs +1 -1
  52. package/bin/lib/doctor-next-actions.mjs +19 -4
  53. package/bin/lib/doctor-plan.mjs +9 -13
  54. package/bin/lib/duplication-io.mjs +572 -0
  55. package/bin/lib/effective-contract-load.mjs +282 -83
  56. package/bin/lib/enforcement-profiles.mjs +40 -4
  57. package/bin/lib/entry-points-io.mjs +424 -0
  58. package/bin/lib/extra-merge-teeth.mjs +36 -0
  59. package/bin/lib/field-install.mjs +38 -14
  60. package/bin/lib/first-run-help.mjs +10 -5
  61. package/bin/lib/flat-parent-importers.mjs +139 -0
  62. package/bin/lib/flat-parent-pilot.mjs +40 -100
  63. package/bin/lib/gate-files.mjs +58 -90
  64. package/bin/lib/git-change-scope.mjs +109 -0
  65. package/bin/lib/github-enforcement.mjs +166 -29
  66. package/bin/lib/golden-pattern.mjs +1 -1
  67. package/bin/lib/hook-templates.mjs +17 -15
  68. package/bin/lib/host-support-matrix.mjs +3 -1
  69. package/bin/lib/html-report-advisories.mjs +4 -0
  70. package/bin/lib/html-report-depth.mjs +2 -2
  71. package/bin/lib/html-report.mjs +2 -2
  72. package/bin/lib/import-graph-projection.mjs +112 -0
  73. package/bin/lib/import-resolve.mjs +13 -1
  74. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  75. package/bin/lib/install-migrate.mjs +50 -73
  76. package/bin/lib/invariant-coverage-io.mjs +4 -3
  77. package/bin/lib/invariant-coverage.mjs +85 -11
  78. package/bin/lib/invariant-probe-cli.mjs +347 -0
  79. package/bin/lib/invariant-probe-io.mjs +165 -0
  80. package/bin/lib/invariant-probe-runner.mjs +268 -0
  81. package/bin/lib/invariant-probe-sites.mjs +174 -0
  82. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  83. package/bin/lib/invariant-probe.mjs +461 -0
  84. package/bin/lib/invariant-tests-path.mjs +16 -22
  85. package/bin/lib/literal-path-drift.mjs +4 -2
  86. package/bin/lib/managed-upgrade.mjs +2 -8
  87. package/bin/lib/mcp-adoption.mjs +13 -6
  88. package/bin/lib/mcp-hook-payload.mjs +403 -63
  89. package/bin/lib/orphan-modules-io.mjs +458 -0
  90. package/bin/lib/orphan-modules.mjs +251 -0
  91. package/bin/lib/outside-importers.mjs +197 -0
  92. package/bin/lib/package-manager.mjs +60 -2
  93. package/bin/lib/parse-health.mjs +1 -1
  94. package/bin/lib/passthrough-commands.mjs +83 -0
  95. package/bin/lib/persistence-write-hint.mjs +106 -0
  96. package/bin/lib/pin-dependent-gates.mjs +117 -0
  97. package/bin/lib/policy-delta-io.mjs +219 -14
  98. package/bin/lib/post-green-path.mjs +1 -1
  99. package/bin/lib/prepare-change.mjs +115 -7
  100. package/bin/lib/prepare-write.mjs +2 -2
  101. package/bin/lib/presets.mjs +59 -73
  102. package/bin/lib/product-copy.mjs +1 -6
  103. package/bin/lib/project-root.mjs +31 -62
  104. package/bin/lib/remediation.mjs +23 -4
  105. package/bin/lib/resolved-candidate-facts.mjs +405 -117
  106. package/bin/lib/rules-inventory.mjs +22 -3
  107. package/bin/lib/rules-under-contract.mjs +247 -9
  108. package/bin/lib/scan-files.mjs +36 -4
  109. package/bin/lib/schema-validation.mjs +130 -0
  110. package/bin/lib/sensor-promote-cli.mjs +2 -11
  111. package/bin/lib/sensor-promote-io.mjs +33 -22
  112. package/bin/lib/sensor-promotion.mjs +1 -1
  113. package/bin/lib/skill-catalog-apply.mjs +1 -1
  114. package/bin/lib/skill-install.mjs +42 -38
  115. package/bin/lib/snippet-analysis.mjs +140 -6
  116. package/bin/lib/source-policy.mjs +34 -0
  117. package/bin/lib/start-install-recovery.mjs +72 -2
  118. package/bin/lib/start-preview.mjs +31 -1
  119. package/bin/lib/states-transitions-presence.mjs +3 -3
  120. package/bin/lib/status-command.mjs +35 -22
  121. package/bin/lib/status-manifest.mjs +50 -30
  122. package/bin/lib/status-transition-catalog.mjs +0 -6
  123. package/bin/lib/suggestions.mjs +3 -3
  124. package/bin/lib/team-parliament-io.mjs +76 -88
  125. package/bin/lib/team-parliament.mjs +21 -10
  126. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  127. package/bin/lib/upgrade-command.mjs +2 -24
  128. package/bin/lib/upgrade-outcome.mjs +1 -1
  129. package/bin/lib/upstream-report.mjs +21 -10
  130. package/bin/lib/violations.mjs +10 -9
  131. package/bin/lib/weakest-link.mjs +13 -0
  132. package/bin/lib/write-path-capabilities.mjs +6 -4
  133. package/dist/chunk-A5EIY3HI.cjs +5 -0
  134. package/dist/chunk-AHACEKSV.js +16 -0
  135. package/dist/chunk-SVSQVGFJ.js +5 -0
  136. package/dist/chunk-UN5EH4QV.cjs +16 -0
  137. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
  138. package/dist/configTypes-GPvvdleM.d.ts +257 -0
  139. package/dist/eslint/index.cjs +18 -5
  140. package/dist/eslint/index.d.cts +368 -0
  141. package/dist/eslint/index.d.ts +139 -156
  142. package/dist/eslint/index.js +11 -5
  143. package/dist/index.cjs +36 -35
  144. package/dist/index.d.cts +5547 -0
  145. package/dist/index.d.ts +3283 -198
  146. package/dist/index.js +36 -35
  147. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  148. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  149. package/dist/nestjs/index.cjs +1 -18
  150. package/dist/nestjs/index.d.cts +22 -0
  151. package/dist/nestjs/index.d.ts +1 -3
  152. package/dist/nestjs/index.js +1 -18
  153. package/dist/order/index.cjs +1 -1
  154. package/dist/order/index.d.cts +155 -0
  155. package/dist/order/index.d.ts +15 -3
  156. package/dist/order/index.js +1 -1
  157. package/dist/runtime/index.cjs +13 -30
  158. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
  159. package/dist/runtime/index.d.ts +2741 -133
  160. package/dist/runtime/index.js +13 -30
  161. package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
  162. package/dist/types-CnauMQQL.d.ts +1800 -0
  163. package/docs/README.md +3 -1
  164. package/docs/agent-guide.md +217 -16
  165. package/docs/ai-gates.md +174 -26
  166. package/docs/arkorder.md +32 -15
  167. package/docs/brownfield-adoption.md +5 -1
  168. package/docs/configuration.md +230 -31
  169. package/docs/develop.md +2 -1
  170. package/docs/diagnostics.md +212 -19
  171. package/docs/package-surface.md +35 -9
  172. package/docs/product-voice.md +7 -1
  173. package/package.json +69 -19
  174. package/schemas/ark.analysis-result.schema.json +15 -0
  175. package/schemas/ark.config.schema.json +77 -12
  176. package/schemas/ark.invariant-probe.schema.json +368 -0
  177. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  178. package/schemas/ark.status-manifest.schema.json +22 -0
  179. package/server.json +3 -9
  180. package/templates/agent-skills/README.md +1 -1
  181. package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
  182. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  183. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  184. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  185. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  186. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  187. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  188. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  189. package/templates/arkrules/PersistenceAdapters.json +1 -1
  190. package/templates/arkrules/PresentationAdapters.json +1 -1
  191. package/templates/skills/ark-adopt.md +5 -1
  192. package/templates/skills/ark-contract.md +17 -0
  193. package/templates/skills/ark-coverage.md +2 -0
  194. package/templates/skills/ark-explore.md +7 -0
  195. package/templates/skills/ark-fix.md +20 -0
  196. package/templates/skills/ark-order.md +4 -2
  197. package/templates/skills/ark-place.md +8 -0
  198. package/templates/skills/ark-runtime.md +6 -1
  199. package/bin/lib/ark-order-error.mjs +0 -18
  200. package/bin/lib/ark-order-invariants.mjs +0 -346
  201. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -8,12 +8,40 @@
8
8
  * Pure CLI helper (bin/lib/ark-run-facts.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
+ import { DEFAULT_INTENT_PREFIXES, effectiveIntentPrefixes, looksLikeArkIntent } from './source-policy.mjs';
12
+ /** Closed factory names from ADR 0022 `arkrun-missing-root`. */
11
13
  export const ARKRUN_KERNEL_FACTORY_CALLEES = [
12
14
  'createArkKernel',
13
15
  'createStrictArkKernel',
16
+ 'createLenientArkKernel',
14
17
  'createArkKernelFromConfig',
15
18
  'createStrictArkKernelFromConfig',
19
+ 'createLenientArkKernelFromConfig',
16
20
  ];
21
+ /**
22
+ * NestJS adapter factory sites: `ArkModule.forRoot()` / `ArkModule.forRootAsync()`
23
+ * count as a kernel factory only when `ArkModule` is imported from a kernel module
24
+ * (`arkgate/nestjs`). A bare `.forRoot(` on any other receiver never counts.
25
+ */
26
+ const ARKRUN_NEST_KERNEL_MODULE = 'ArkModule';
27
+ const ARKRUN_NEST_FACTORY_METHODS = ['forRoot', 'forRootAsync'];
28
+ /**
29
+ * Kernel types whose annotated bindings (`ark: ArkKernel`, constructor-injected
30
+ * `private readonly ark: ArkKernel`) are traced as kernel receivers when the type
31
+ * is imported from a kernel module.
32
+ */
33
+ const ARKRUN_KERNEL_RECEIVER_TYPES = [
34
+ 'ArkKernel',
35
+ 'EventBus',
36
+ 'EventPublisher',
37
+ 'ArkRunPublisher',
38
+ ];
39
+ /** Kernel members that still reach the kernel's interaction API (`ark.eventBus.publish`). */
40
+ const KERNEL_TRANSIT_MEMBERS = new Set(['eventBus']);
41
+ /** Members whose value is a kernel-bound interaction object (`const pub = ark.publisher(..)`). */
42
+ const KERNEL_DERIVED_MEMBERS = new Set(['publisher', 'eventBus']);
43
+ const NEST_FACTORY_METHODS = new Set(ARKRUN_NEST_FACTORY_METHODS);
44
+ const RECEIVER_TYPES = new Set(ARKRUN_KERNEL_RECEIVER_TYPES);
17
45
  /** Closed interaction callees from ADR 0022 undeclared-emit/handle/depend. */
18
46
  export const ARKRUN_KERNEL_INTERACTION_CALLEES = [
19
47
  'publisher',
@@ -91,7 +119,48 @@ export function isArkRunKernelModuleSpecifier(specifier) {
91
119
  return (specifier === '@arkgate/runtime' ||
92
120
  specifier.startsWith('@arkgate/runtime/') ||
93
121
  specifier === 'arkgate/runtime' ||
94
- specifier.startsWith('arkgate/runtime/'));
122
+ specifier.startsWith('arkgate/runtime/') ||
123
+ // NestJS adapter re-exports the kernel (ArkModule, InjectArk, ArkKernel).
124
+ specifier === 'arkgate/nestjs' ||
125
+ specifier.startsWith('arkgate/nestjs/'));
126
+ }
127
+ function stripScriptExtension(path) {
128
+ return path.replace(/\.(?:[cm]?[jt]sx?)$/i, '');
129
+ }
130
+ function joinRelative(fromFile, specifier) {
131
+ const parts = fromFile.replace(/\\/g, '/').split('/');
132
+ parts.pop();
133
+ for (const piece of specifier.split('/')) {
134
+ if (piece === '' || piece === '.')
135
+ continue;
136
+ if (piece === '..')
137
+ parts.pop();
138
+ else
139
+ parts.push(piece);
140
+ }
141
+ return parts.join('/');
142
+ }
143
+ /**
144
+ * Pure predicate: does a relative `specifier` imported from `fromFile` point at
145
+ * one of the ArkRun kernel/composition roots? Identifiers imported from such a
146
+ * module are traced as kernel receivers (an `ark` binding imported from a relative `../main`).
147
+ */
148
+ export function createArkRunKernelRootSpecifierMatcher(fromFile, roots) {
149
+ return (specifier) => {
150
+ if (!specifier.startsWith('.'))
151
+ return false;
152
+ const target = stripScriptExtension(joinRelative(fromFile, specifier));
153
+ return roots.some((root) => {
154
+ const pattern = root.replace(/\\/g, '/');
155
+ const star = pattern.indexOf('*');
156
+ if (star >= 0) {
157
+ const prefix = pattern.slice(0, star).replace(/\/$/, '');
158
+ return prefix.length > 0 && (target === prefix || target.startsWith(`${prefix}/`));
159
+ }
160
+ const exact = stripScriptExtension(pattern);
161
+ return exact === target || exact === `${target}/index`;
162
+ });
163
+ };
95
164
  }
96
165
  /**
97
166
  * Closed broker / queue / emitter specifiers for `arkrun-transport-bypass`
@@ -241,21 +310,33 @@ export function extractArkRunValueImportDependenciesFromSource(file, content) {
241
310
  }
242
311
  return out;
243
312
  }
313
+ /**
314
+ * Value bindings of a braced import list (`{ A, B as C }`): local name plus the
315
+ * imported (original) name. Inline `type` specifiers are skipped.
316
+ */
317
+ function bracedValueImportBindings(clause) {
318
+ const braced = /\{([^}]*)\}/.exec(clause);
319
+ if (!braced?.[1])
320
+ return [];
321
+ const out = [];
322
+ for (const part of braced[1].split(',')) {
323
+ const piece = part.trim();
324
+ if (!piece || piece.startsWith('type '))
325
+ continue;
326
+ const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
327
+ const local = alias?.[2] ?? /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece)?.[1];
328
+ const original = alias?.[1] ?? local;
329
+ if (local && original)
330
+ out.push({ local, original });
331
+ }
332
+ return out;
333
+ }
244
334
  /** PascalCase named bindings from value import clauses (snippet admitted constructors). */
245
335
  export function extractArkRunImportedConstructorNamesFromSource(content) {
246
336
  const names = [];
247
337
  forEachArkRunValueImportClause(content, (clause) => {
248
- const braced = /\{([^}]*)\}/.exec(clause);
249
- if (!braced?.[1])
250
- return;
251
- for (const part of braced[1].split(',')) {
252
- const piece = part.trim();
253
- if (!piece || piece.startsWith('type '))
254
- continue;
255
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
256
- const local = alias?.[2] ?? /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece)?.[1];
257
- const original = alias?.[1] ?? local;
258
- if (local && original && /^[A-Z]/.test(original))
338
+ for (const { local, original } of bracedValueImportBindings(clause)) {
339
+ if (/^[A-Z]/.test(original))
259
340
  names.push(local, original);
260
341
  }
261
342
  });
@@ -273,21 +354,8 @@ function collectKernelImportBindings(content) {
273
354
  const defaultIdent = /^([A-Za-z_][A-Za-z0-9_]*)\s*(?:,|$)/.exec(clause.trim());
274
355
  if (defaultIdent?.[1])
275
356
  named.set(defaultIdent[1], defaultIdent[1]);
276
- const braced = /\{([^}]*)\}/.exec(clause);
277
- if (!braced?.[1])
278
- return;
279
- for (const part of braced[1].split(',')) {
280
- const piece = part.trim();
281
- if (!piece || piece.startsWith('type '))
282
- continue;
283
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
284
- if (alias) {
285
- named.set(alias[2], alias[1]);
286
- continue;
287
- }
288
- const ident = /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
289
- if (ident?.[1])
290
- named.set(ident[1], ident[1]);
357
+ for (const { local, original } of bracedValueImportBindings(clause)) {
358
+ named.set(local, original);
291
359
  }
292
360
  });
293
361
  return { named, namespaces };
@@ -295,17 +363,8 @@ function collectKernelImportBindings(content) {
295
363
  function collectImportedConstructors(content, admitted) {
296
364
  const out = new Set(admitted);
297
365
  parseValueImportClause(content, (clause, specifier) => {
298
- const braced = /\{([^}]*)\}/.exec(clause);
299
- if (!braced?.[1])
300
- return;
301
- for (const part of braced[1].split(',')) {
302
- const piece = part.trim();
303
- if (!piece || piece.startsWith('type '))
304
- continue;
305
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
306
- const local = alias?.[2] ?? /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece)?.[1];
307
- const original = alias?.[1] ?? local;
308
- if (!local || !original || !/^[A-Z]/.test(original))
366
+ for (const { local, original } of bracedValueImportBindings(clause)) {
367
+ if (!/^[A-Z]/.test(original))
309
368
  continue;
310
369
  if (isArkRunKernelModuleSpecifier(specifier) || admitted.has(original) || admitted.has(local)) {
311
370
  out.add(local);
@@ -323,9 +382,347 @@ function importedFromForName(content, localName) {
323
382
  });
324
383
  return found;
325
384
  }
326
- export function extractArkRunKernelCallsFromSource(file, content) {
385
+ /** Locals of kernel receiver types imported (value or type-only) from a kernel module. */
386
+ function collectKernelTypeNames(source) {
387
+ const out = new Set();
388
+ const importRe = /\bimport\s+(?:type\s+)?([\s\S]*?)\s+from\s*['"]([^'"]+)['"]/g;
389
+ let match;
390
+ while ((match = importRe.exec(source)) !== null) {
391
+ if (!isArkRunKernelModuleSpecifier(match[2] ?? ''))
392
+ continue;
393
+ const braced = /\{([^}]*)\}/.exec(match[1] ?? '');
394
+ if (!braced?.[1])
395
+ continue;
396
+ for (const part of braced[1].split(',')) {
397
+ const piece = part.trim().replace(/^type\s+/, '');
398
+ const alias = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(piece);
399
+ const original = alias?.[1] ?? piece;
400
+ const local = alias?.[2] ?? piece;
401
+ if (RECEIVER_TYPES.has(original))
402
+ out.add(local);
403
+ }
404
+ }
405
+ return out;
406
+ }
407
+ function isFactoryCalleeExpression(expression, bindings) {
408
+ const parts = expression.split('.');
409
+ if (parts.length === 2) {
410
+ return bindings.namespaces.has(parts[0]) && FACTORY_CALLEES.has(parts[1]);
411
+ }
412
+ if (parts.length !== 1)
413
+ return false;
414
+ const original = bindings.named.get(parts[0]);
415
+ return original !== undefined && FACTORY_CALLEES.has(original);
416
+ }
417
+ function addRootModuleImports(source, isKernelRootSpecifier, receivers, rootNamespaces) {
418
+ parseValueImportClause(source, (clause, specifier) => {
419
+ if (!isKernelRootSpecifier(specifier))
420
+ return;
421
+ const namespace = /\*\s+as\s+([A-Za-z_$][\w$]*)/.exec(clause);
422
+ if (namespace?.[1]) {
423
+ receivers.add(namespace[1]);
424
+ rootNamespaces?.add(namespace[1]);
425
+ }
426
+ const defaultIdent = /^([A-Za-z_$][\w$]*)\s*(?:,|$)/.exec(clause.trim());
427
+ if (defaultIdent?.[1])
428
+ receivers.add(defaultIdent[1]);
429
+ const braced = /\{([^}]*)\}/.exec(clause);
430
+ for (const part of braced?.[1]?.split(',') ?? []) {
431
+ const piece = part.trim();
432
+ if (!piece || piece.startsWith('type '))
433
+ continue;
434
+ const local = /([A-Za-z_$][\w$]*)$/.exec(piece)?.[1];
435
+ if (local)
436
+ receivers.add(local);
437
+ }
438
+ });
439
+ }
440
+ /** Derived bindings to a fixpoint: `const pub = ark.publisher(..)`, `{ eventBus, send } = ark`. */
441
+ function addDerivedReceivers(source, trace) {
442
+ const { receivers, calleeBindings } = trace;
443
+ const derived = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=;]+)?=\s*(?:await\s+)?(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)\b/g;
444
+ const destructured = /\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*[;\n)]/g;
445
+ // Plain aliasing: `const kernel = ark;`, `this.kernel = ark;`, `kernel = this.ark;`.
446
+ const aliased = /(?:\b(?:const|let|var)\s+|\bthis\s*\.\s*|(?:^|[;{}\n])\s*)([A-Za-z_$][\w$]*)\s*(?::[^=;]+)?(?<![=!<>])=(?![=>])\s*(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*(?:as\s+[A-Za-z_$][\w$.<>]*\s*)?(?=[;\n,)])/g;
447
+ // `kernel: typeof ark` (parameters, fields) — the binding has the kernel's type.
448
+ const typeofBound = /([A-Za-z_$][\w$]*)\s*\??\s*:\s*typeof\s+([A-Za-z_$][\w$]*)\b/g;
449
+ let match;
450
+ for (let changed = true; changed;) {
451
+ changed = false;
452
+ for (const re of [aliased, typeofBound]) {
453
+ re.lastIndex = 0;
454
+ while ((match = re.exec(source)) !== null) {
455
+ if (!receivers.has(match[2]) || receivers.has(match[1]))
456
+ continue;
457
+ receivers.add(match[1]);
458
+ changed = true;
459
+ }
460
+ }
461
+ derived.lastIndex = 0;
462
+ while ((match = derived.exec(source)) !== null) {
463
+ if (!receivers.has(match[2]))
464
+ continue;
465
+ // `const k = main.ark` on a root namespace is a named-import equivalent.
466
+ if (!KERNEL_DERIVED_MEMBERS.has(match[3]) && !trace.rootNamespaces.has(match[2])) {
467
+ continue;
468
+ }
469
+ if (receivers.has(match[1]))
470
+ continue;
471
+ receivers.add(match[1]);
472
+ changed = true;
473
+ }
474
+ destructured.lastIndex = 0;
475
+ while ((match = destructured.exec(source)) !== null) {
476
+ if (!receivers.has(match[2]))
477
+ continue;
478
+ for (const part of match[1].split(',')) {
479
+ const pair = /^\s*([A-Za-z_$][\w$]*)\s*(?::\s*([A-Za-z_$][\w$]*))?\s*$/.exec(part);
480
+ if (!pair)
481
+ continue;
482
+ const member = pair[1];
483
+ const local = pair[2] ?? member;
484
+ if (KERNEL_TRANSIT_MEMBERS.has(member)) {
485
+ if (receivers.has(local))
486
+ continue;
487
+ receivers.add(local);
488
+ changed = true;
489
+ }
490
+ else if (arkRunKernelCallKind(member) && !FACTORY_CALLEES.has(member)) {
491
+ calleeBindings.add(local);
492
+ }
493
+ }
494
+ }
495
+ }
496
+ }
497
+ /**
498
+ * Deterministic kernel-receiver trace (ADR 0022 D1): a non-factory interaction is
499
+ * a kernel call only when its receiver provably reaches the kernel. Matching on a
500
+ * method name alone (`res.send`, `require.resolve`, `subject.subscribe`) is not
501
+ * evidence.
502
+ */
503
+ function traceKernelReceivers(source, bindings, isKernelRootSpecifier) {
504
+ const trace = {
505
+ receivers: new Set(),
506
+ calleeBindings: new Set(),
507
+ rootNamespaces: new Set(),
508
+ };
509
+ if (isKernelRootSpecifier) {
510
+ addRootModuleImports(source, isKernelRootSpecifier, trace.receivers, trace.rootNamespaces);
511
+ }
512
+ const factoryBound = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=;]+)?=\s*(?:await\s+)?((?:[A-Za-z_$][\w$]*\s*\.\s*)?[A-Za-z_$][\w$]*)\s*(?:<[^>]*>)?\s*\(/g;
513
+ let match;
514
+ while ((match = factoryBound.exec(source)) !== null) {
515
+ if (isFactoryCalleeExpression(match[2].replace(/\s+/g, ''), bindings)) {
516
+ trace.receivers.add(match[1]);
517
+ }
518
+ }
519
+ const typeNames = collectKernelTypeNames(source);
520
+ if (typeNames.size > 0) {
521
+ const annotated = /([A-Za-z_$][\w$]*)\s*\??\s*:\s*(?:Readonly\s*<\s*)?([A-Za-z_$][\w$]*)\b/g;
522
+ while ((match = annotated.exec(source)) !== null) {
523
+ if (typeNames.has(match[2]))
524
+ trace.receivers.add(match[1]);
525
+ }
526
+ }
527
+ addDerivedReceivers(source, trace);
528
+ return trace;
529
+ }
530
+ /** Index of the `(` matching the `)` at `closeIndex`, or -1. */
531
+ function matchingOpenParen(source, closeIndex) {
532
+ let depth = 0;
533
+ for (let i = closeIndex; i >= 0; i -= 1) {
534
+ const ch = source[i];
535
+ if (ch === ')')
536
+ depth += 1;
537
+ else if (ch === '(') {
538
+ depth -= 1;
539
+ if (depth === 0)
540
+ return i;
541
+ }
542
+ }
543
+ return -1;
544
+ }
545
+ function chainParts(text) {
546
+ const parts = text
547
+ .split('.')
548
+ .map((part) => part.trim())
549
+ .filter((part) => part.length > 0);
550
+ return parts[0] === 'this' ? parts.slice(1) : parts;
551
+ }
552
+ /**
553
+ * Member chain before a call (`this.ark.eventBus.publish(` → [ark, eventBus]).
554
+ * A chained `ark.publisher('S').send(..)` yields the `publisher` call's chain.
555
+ */
556
+ function receiverChainBefore(source, index) {
557
+ const head = source.slice(Math.max(0, index - 400), index);
558
+ const dotted = /((?:[A-Za-z_$][\w$]*\s*\??\.\s*)+)$/.exec(head);
559
+ if (dotted?.[1]) {
560
+ const before = head.slice(0, head.length - dotted[1].length);
561
+ // `a[0].send(` / `fn().send(`: the chain root is not a plain identifier.
562
+ if (/[)\]]\s*$/.test(before) || /\.\s*$/.test(before)) {
563
+ return { parts: ['<expr>'], viaPublisherCall: false };
564
+ }
565
+ return { parts: chainParts(dotted[1].replace(/\?/g, '')), viaPublisherCall: false };
566
+ }
567
+ const chained = /\)\s*\??\.\s*$/.exec(head);
568
+ if (!chained)
569
+ return undefined;
570
+ const closeIndex = index - head.length + chained.index;
571
+ const openIndex = matchingOpenParen(source, closeIndex);
572
+ if (openIndex < 0)
573
+ return { parts: ['<expr>'], viaPublisherCall: false };
574
+ const calleeHead = source.slice(Math.max(0, openIndex - 400), openIndex);
575
+ const callee = /((?:[A-Za-z_$][\w$]*\s*\??\.\s*)+)publisher\s*(?:<[^>]*>)?\s*$/.exec(calleeHead);
576
+ if (!callee?.[1])
577
+ return { parts: ['<expr>'], viaPublisherCall: false };
578
+ return { parts: chainParts(callee[1].replace(/\?/g, '')), viaPublisherCall: true };
579
+ }
580
+ function chainReachesKernel(chain, bindings, trace) {
581
+ const [root, ...rest] = chain.parts;
582
+ if (!root)
583
+ return false;
584
+ if (rest.length === 0 && bindings.namespaces.has(root))
585
+ return true;
586
+ // Backstop: ambient receivers are never the kernel, even if shadowed by a trace.
587
+ if (SKIP_INTERACTION_RECEIVERS.has(root) || !trace.receivers.has(root))
588
+ return false;
589
+ // `main.ark.publisher(..)`: the first hop off a root namespace is an exported
590
+ // binding of the composition root — the same receiver `import { ark }` traces.
591
+ const transit = trace.rootNamespaces.has(root) ? rest.slice(1) : rest;
592
+ return transit.every((member) => KERNEL_TRANSIT_MEMBERS.has(member));
593
+ }
594
+ /**
595
+ * Same-file intent names: `const X = ark.registry.define('N')` / `defineIntent('N')`,
596
+ * `define(N_CONST)`, and `const N = 'Name'` constants (ADR 0023 D2: define names
597
+ * count as call-site names). Imported creators stay unresolved (honest partial).
598
+ */
599
+ function collectDefinedIntentNames(source) {
600
+ // Not scope-aware: an identifier bound to two different names anywhere in the
601
+ // file is ambiguous (a function-local shadow could differ), so it resolves to
602
+ // nothing and the call stays an honest partial instead of a false green.
603
+ const ambiguous = new Set();
604
+ const bind = (map, id, name) => {
605
+ const prior = map.get(id);
606
+ if (prior !== undefined && prior !== name)
607
+ ambiguous.add(id);
608
+ else
609
+ map.set(id, name);
610
+ };
611
+ const constants = new Map();
612
+ const constRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=;]+)?=\s*(['"])((?:\\.|[^\\])*?)\2\s*(?:as\s+const\s*)?[;\n,)]/g;
613
+ let match;
614
+ while ((match = constRe.exec(source)) !== null) {
615
+ if (match[3])
616
+ bind(constants, match[1], match[3]);
617
+ }
618
+ for (const id of ambiguous)
619
+ constants.delete(id);
620
+ const defined = new Map(constants);
621
+ const defineRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=;]+)?=\s*(?:[A-Za-z_$][\w$]*\s*\.\s*)*(?:define|defineIntent)\s*(?:<[^>]*>)?\s*\(\s*(?:(['"])((?:\\.|[^\\])*?)\2|([A-Za-z_$][\w$]*)\s*[,)])/g;
622
+ while ((match = defineRe.exec(source)) !== null) {
623
+ const name = match[3] ?? (match[4] ? constants.get(match[4]) : undefined);
624
+ if (name)
625
+ bind(defined, match[1], name);
626
+ else if (ambiguous.has(match[4] ?? '') || defined.has(match[1]))
627
+ ambiguous.add(match[1]);
628
+ }
629
+ for (const id of ambiguous)
630
+ defined.delete(id);
631
+ return defined;
632
+ }
633
+ function firstIdentifierArg(content, openParenEnd) {
634
+ return /^\s*([A-Za-z_$][\w$]*)\s*[,)]/.exec(content.slice(openParenEnd))?.[1];
635
+ }
636
+ /**
637
+ * Effective intent prefixes of a config: each layer's declared `intentPrefixes`
638
+ * (or its canonical defaults) plus every canonical prefix. The kernel maps intents
639
+ * by these, so a name carrying one is kernel-valid evidence.
640
+ */
641
+ export function arkRunEffectiveIntentPrefixes(config) {
642
+ const prefixes = new Set();
643
+ for (const entry of DEFAULT_INTENT_PREFIXES) {
644
+ for (const prefix of entry.prefixes)
645
+ prefixes.add(prefix);
646
+ }
647
+ for (const layer of config.layers) {
648
+ for (const prefix of effectiveIntentPrefixes(layer))
649
+ prefixes.add(prefix);
650
+ }
651
+ return [...prefixes].sort();
652
+ }
653
+ function carriesIntentPrefix(value, prefixes) {
654
+ if (looksLikeArkIntent(value))
655
+ return true;
656
+ return (prefixes ?? []).some((raw) => {
657
+ const prefix = raw.trim();
658
+ if (!prefix)
659
+ return false;
660
+ const normalized = prefix.endsWith('.') ? prefix : `${prefix}.`;
661
+ return value.startsWith(normalized) && /^[A-Za-z0-9_.]+$/.test(value.slice(normalized.length));
662
+ });
663
+ }
664
+ function receiverName(chain) {
665
+ if (!chain)
666
+ return undefined;
667
+ if (chain.viaPublisherCall)
668
+ return 'publisher';
669
+ const last = chain.parts[chain.parts.length - 1];
670
+ return last === '<expr>' ? undefined : last;
671
+ }
672
+ /**
673
+ * A receiver the trace cannot follow (untyped parameter, cross-file hop the
674
+ * resolver did not see) still counts when the call names a kernel-valid intent
675
+ * (`Domain.…`, or a configured `intentPrefixes` prefix such as `Billing.…` — the only
676
+ * names a kernel maps). `res.send('ok')`
677
+ * or `require.resolve('pkg')` never carry such a name; ambient receivers never count.
678
+ */
679
+ function untracedKernelNameEvidence(chain, stringArg, intentPrefixes) {
680
+ if (!chain || stringArg === undefined || !carriesIntentPrefix(stringArg, intentPrefixes)) {
681
+ return false;
682
+ }
683
+ const root = chain.parts[0];
684
+ return root !== undefined && root !== '<expr>' && !SKIP_INTERACTION_RECEIVERS.has(root);
685
+ }
686
+ /**
687
+ * Does this module re-export a kernel root (a named or star re-export of a relative
688
+ * `main` module, or an `ark` binding imported from it and re-exported /
689
+ * `export const kernel = ark` / `export default ark`)? The resolver uses it to
690
+ * treat barrels as kernel roots, to a fixpoint.
691
+ */
692
+ export function reexportsArkRunKernelRoot(content, isKernelRootSpecifier) {
693
+ const source = stripCommentsPreservingLines(content);
694
+ const reexport = /\bexport\s+(?!type\b)(?:\*|\{[^}]*\}|\*\s+as\s+[A-Za-z_$][\w$]*)\s*from\s*['"]([^'"]+)['"]/g;
695
+ let found;
696
+ while ((found = reexport.exec(source)) !== null) {
697
+ if (isKernelRootSpecifier(found[1] ?? ''))
698
+ return true;
699
+ }
700
+ const imported = new Set();
701
+ addRootModuleImports(source, isKernelRootSpecifier, imported);
702
+ if (imported.size === 0)
703
+ return false;
704
+ const exportedLocal = [
705
+ /\bexport\s*\{([^}]*)\}\s*(?!\s*from)/g,
706
+ /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*[;\n]/g,
707
+ /\bexport\s+(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*(?::[^=;]+)?=\s*([A-Za-z_$][\w$]*)\s*[;\n]/g,
708
+ ];
709
+ for (const re of exportedLocal) {
710
+ let match;
711
+ while ((match = re.exec(source)) !== null) {
712
+ const names = (match[1] ?? '')
713
+ .split(',')
714
+ .map((part) => part.trim().split(/\s+as\s+/)[0]?.trim() ?? '');
715
+ if (names.some((name) => imported.has(name)))
716
+ return true;
717
+ }
718
+ }
719
+ return false;
720
+ }
721
+ export function extractArkRunKernelCallsFromSource(file, content, options = {}) {
327
722
  const source = stripCommentsPreservingLines(content);
328
723
  const bindings = collectKernelImportBindings(source);
724
+ const trace = traceKernelReceivers(source, bindings, options.isKernelRootSpecifier);
725
+ const definedNames = collectDefinedIntentNames(source);
329
726
  const facts = [];
330
727
  const callRe = /\b([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]*>)?\s*\(/g;
331
728
  let match;
@@ -334,20 +731,36 @@ export function extractArkRunKernelCallsFromSource(file, content) {
334
731
  const index = match.index;
335
732
  if (keywordBefore(source, index, 'function') || keywordBefore(source, index, 'class'))
336
733
  continue;
337
- const dotted = source.slice(0, index).match(/([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*$/);
338
- const receiver = dotted?.[1];
734
+ const chain = receiverChainBefore(source, index);
735
+ const receiver = receiverName(chain);
339
736
  const original = bindings.named.get(callee) ?? callee;
340
- const kind = arkRunKernelCallKind(original) ?? arkRunKernelCallKind(callee);
737
+ let kind = arkRunKernelCallKind(original) ?? arkRunKernelCallKind(callee);
738
+ let viaImport = bindings.named.has(callee) ||
739
+ trace.calleeBindings.has(callee) ||
740
+ (chain?.parts.length === 1 && bindings.namespaces.has(chain.parts[0]));
741
+ if (!kind &&
742
+ NEST_FACTORY_METHODS.has(callee) &&
743
+ chain?.parts.length === 1 &&
744
+ bindings.named.get(chain.parts[0]) === ARKRUN_NEST_KERNEL_MODULE) {
745
+ kind = 'factory';
746
+ viaImport = true;
747
+ }
341
748
  if (!kind)
342
749
  continue;
343
- const viaImport = bindings.named.has(callee) || (receiver !== undefined && bindings.namespaces.has(receiver));
344
- if (kind !== 'factory') {
345
- if (!viaImport && receiver === undefined)
346
- continue;
347
- if (receiver && SKIP_INTERACTION_RECEIVERS.has(receiver) && !viaImport)
750
+ if (kind === 'factory') {
751
+ // `obj.createArkKernel()` on an arbitrary receiver is not the kernel factory.
752
+ if (chain !== undefined && !viaImport)
348
753
  continue;
349
754
  }
350
- const nameLiteral = firstStringLiteralArg(source, index + match[0].length);
755
+ const argStart = index + match[0].length;
756
+ const stringArg = firstStringLiteralArg(source, argStart);
757
+ if (kind !== 'factory' &&
758
+ (chain === undefined ? !viaImport : !chainReachesKernel(chain, bindings, trace)) &&
759
+ !untracedKernelNameEvidence(chain, stringArg, options.intentPrefixes)) {
760
+ continue;
761
+ }
762
+ const identifierArg = firstIdentifierArg(source, argStart);
763
+ const nameLiteral = stringArg ?? (identifierArg ? definedNames.get(identifierArg) : undefined);
351
764
  facts.push({
352
765
  file,
353
766
  line: lineAt(content, index),
@@ -30,7 +30,7 @@ export const ARKRUN_EDITOR_SENSOR_IDS = [
30
30
  'arkrun-transport-bypass',
31
31
  ];
32
32
  const EDITOR_SENSOR_SET = new Set(ARKRUN_EDITOR_SENSOR_IDS);
33
- export function isArkRunEditorSensor(sensor) {
33
+ function isArkRunEditorSensor(sensor) {
34
34
  return EDITOR_SENSOR_SET.has(sensor);
35
35
  }
36
36
  export const ARKRUN_RULE_IDS = {
@@ -132,7 +132,7 @@ function evaluateMissingRoot(extra, hits, teethAllowed) {
132
132
  if (matched.some((hit) => hit.hasKernelFactory))
133
133
  continue;
134
134
  const first = matched[0];
135
- out.push(finding(extra, 'arkrun-missing-root', first.file, 1, `ArkRun kernel root ${JSON.stringify(pattern)} has no createArkKernel / createStrictArkKernel factory.`, { target: pattern }, teethAllowed));
135
+ out.push(finding(extra, 'arkrun-missing-root', first.file, 1, `ArkRun kernel root ${JSON.stringify(pattern)} has no kernel factory (createArkKernel / createStrictArkKernel / createLenientArkKernel / *FromConfig, or ArkModule.forRoot / forRootAsync from arkgate/nestjs).`, { target: pattern }, teethAllowed));
136
136
  }
137
137
  return out;
138
138
  }
@@ -194,11 +194,13 @@ function evaluateUndeclared(extra, kernelCalls, declarations, layerForFile, teet
194
194
  if (!fromLayer || !managed.has(fromLayer))
195
195
  continue;
196
196
  if (!call.nameLiteral) {
197
- if (extra.mode === 'enforced') {
197
+ // `publisher(creator)` binds a source; the emitted name is proven at the
198
+ // chained `.publish` / `.send`, so an unresolved source is not a gap.
199
+ if (extra.mode === 'enforced' && call.kind !== 'publisher') {
198
200
  completenessReasons.push({
199
201
  code: ARKRUN_INTERACTION_NAME_INCOMPLETE,
200
202
  file: call.file,
201
- message: `ArkRun ${call.kind} call in ${call.file} has no string-literal name; enforced extra cannot prove the declaration.`,
203
+ message: `ArkRun ${call.kind} call in ${call.file} has no string-literal name or same-file define/defineIntent binding; enforced extra cannot prove the declaration.`,
202
204
  });
203
205
  }
204
206
  continue;