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.
- package/CHANGELOG.md +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- 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
|
|
249
|
-
|
|
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
|
|
277
|
-
|
|
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
|
|
299
|
-
|
|
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
|
-
|
|
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
|
|
338
|
-
const receiver =
|
|
734
|
+
const chain = receiverChainBefore(source, index);
|
|
735
|
+
const receiver = receiverName(chain);
|
|
339
736
|
const original = bindings.named.get(callee) ?? callee;
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|