arkgate 4.8.24 → 4.8.26

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 (166) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +2 -2
  3. package/bin/ark-check-runtime.mjs +17 -14
  4. package/bin/ark-layer-match.mjs +115 -13
  5. package/bin/ark-mcp-runtime.mjs +36 -31
  6. package/bin/ark-shared.mjs +4 -23
  7. package/bin/lib/adoption-stance.mjs +2 -2
  8. package/bin/lib/adr-path.mjs +3 -3
  9. package/bin/lib/agent-gates.mjs +0 -61
  10. package/bin/lib/agent-homes.mjs +12 -21
  11. package/bin/lib/agent-projection-command.mjs +4 -4
  12. package/bin/lib/ambient-state.mjs +9 -30
  13. package/bin/lib/analysis-engine.mjs +14 -14
  14. package/bin/lib/architecture-scan.mjs +11 -9
  15. package/bin/lib/ark-order-doctor.mjs +3 -28
  16. package/bin/lib/ark-order-facts.mjs +6 -99
  17. package/bin/lib/ark-order-types.mjs +1 -2
  18. package/bin/lib/ark-run-doctor.mjs +3 -28
  19. package/bin/lib/ark-run-facts.mjs +30 -40
  20. package/bin/lib/ark-run-sensors.mjs +1 -1
  21. package/bin/lib/arkrule-file-hints.mjs +2 -47
  22. package/bin/lib/arkrules-contract.mjs +124 -133
  23. package/bin/lib/arkrules-sensors.mjs +7 -5
  24. package/bin/lib/check-args.mjs +63 -1
  25. package/bin/lib/ci-and-commands.mjs +8 -72
  26. package/bin/lib/ci-merge-boundary.mjs +1 -1
  27. package/bin/lib/class-source-scan.mjs +5 -5
  28. package/bin/lib/clone-detection.mjs +474 -0
  29. package/bin/lib/codex-home.mjs +1 -1
  30. package/bin/lib/config-contract-slices.mjs +44 -6
  31. package/bin/lib/config-contract.mjs +40 -117
  32. package/bin/lib/config-extras.mjs +16 -22
  33. package/bin/lib/contract-smells.mjs +33 -15
  34. package/bin/lib/core-ratchet.mjs +0 -2
  35. package/bin/lib/dashboard-cli.mjs +2 -2
  36. package/bin/lib/deep-module-coach.mjs +2 -4
  37. package/bin/lib/deploy-path.mjs +13 -38
  38. package/bin/lib/design-smells.mjs +2 -2
  39. package/bin/lib/diagnostic-catalog.mjs +8 -1
  40. package/bin/lib/doctor-advisories.mjs +55 -9
  41. package/bin/lib/doctor-green-cite.mjs +1 -1
  42. package/bin/lib/doctor-human.mjs +1 -1
  43. package/bin/lib/doctor-next-actions.mjs +2 -2
  44. package/bin/lib/doctor-plan.mjs +4 -6
  45. package/bin/lib/duplication-io.mjs +572 -0
  46. package/bin/lib/effective-contract-load.mjs +181 -44
  47. package/bin/lib/entry-points-io.mjs +424 -0
  48. package/bin/lib/extra-merge-teeth.mjs +36 -0
  49. package/bin/lib/field-install.mjs +38 -14
  50. package/bin/lib/first-run-help.mjs +7 -3
  51. package/bin/lib/flat-parent-importers.mjs +5 -3
  52. package/bin/lib/gate-files.mjs +30 -89
  53. package/bin/lib/github-enforcement.mjs +165 -28
  54. package/bin/lib/golden-pattern.mjs +1 -1
  55. package/bin/lib/hook-templates.mjs +16 -16
  56. package/bin/lib/html-report-advisories.mjs +4 -0
  57. package/bin/lib/html-report-depth.mjs +2 -2
  58. package/bin/lib/html-report.mjs +2 -2
  59. package/bin/lib/import-graph-projection.mjs +112 -0
  60. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  61. package/bin/lib/invariant-coverage-io.mjs +4 -3
  62. package/bin/lib/invariant-coverage.mjs +26 -0
  63. package/bin/lib/invariant-probe-cli.mjs +347 -0
  64. package/bin/lib/invariant-probe-io.mjs +165 -0
  65. package/bin/lib/invariant-probe-runner.mjs +268 -0
  66. package/bin/lib/invariant-probe-sites.mjs +174 -0
  67. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  68. package/bin/lib/invariant-probe.mjs +461 -0
  69. package/bin/lib/invariant-tests-path.mjs +16 -22
  70. package/bin/lib/literal-path-drift.mjs +4 -2
  71. package/bin/lib/managed-upgrade.mjs +2 -8
  72. package/bin/lib/mcp-adoption.mjs +4 -5
  73. package/bin/lib/mcp-hook-payload.mjs +4 -4
  74. package/bin/lib/orphan-modules-io.mjs +458 -0
  75. package/bin/lib/orphan-modules.mjs +251 -0
  76. package/bin/lib/outside-importers.mjs +197 -0
  77. package/bin/lib/package-manager.mjs +3 -3
  78. package/bin/lib/parse-health.mjs +1 -1
  79. package/bin/lib/persistence-write-hint.mjs +33 -8
  80. package/bin/lib/pin-dependent-gates.mjs +2 -2
  81. package/bin/lib/policy-delta-io.mjs +60 -12
  82. package/bin/lib/post-green-path.mjs +1 -1
  83. package/bin/lib/prepare-change.mjs +3 -9
  84. package/bin/lib/prepare-write.mjs +1 -2
  85. package/bin/lib/presets.mjs +56 -68
  86. package/bin/lib/product-copy.mjs +1 -6
  87. package/bin/lib/project-root.mjs +43 -85
  88. package/bin/lib/resolved-candidate-facts.mjs +90 -62
  89. package/bin/lib/rules-under-contract.mjs +72 -8
  90. package/bin/lib/scan-files.mjs +4 -4
  91. package/bin/lib/schema-validation.mjs +130 -0
  92. package/bin/lib/sensor-promote-cli.mjs +2 -11
  93. package/bin/lib/sensor-promote-io.mjs +33 -22
  94. package/bin/lib/sensor-promotion.mjs +1 -1
  95. package/bin/lib/skill-catalog-apply.mjs +1 -1
  96. package/bin/lib/skill-install.mjs +42 -38
  97. package/bin/lib/snippet-analysis.mjs +1 -1
  98. package/bin/lib/start-install-recovery.mjs +1 -1
  99. package/bin/lib/start-preview.mjs +1 -1
  100. package/bin/lib/states-transitions-presence.mjs +3 -3
  101. package/bin/lib/status-command.mjs +2 -18
  102. package/bin/lib/status-manifest.mjs +18 -26
  103. package/bin/lib/status-transition-catalog.mjs +0 -6
  104. package/bin/lib/suggestions.mjs +3 -3
  105. package/bin/lib/team-parliament-io.mjs +3 -17
  106. package/bin/lib/team-parliament.mjs +15 -9
  107. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  108. package/bin/lib/upgrade-command.mjs +2 -24
  109. package/bin/lib/upgrade-outcome.mjs +1 -1
  110. package/bin/lib/upstream-report.mjs +21 -10
  111. package/bin/lib/violations.mjs +10 -9
  112. package/bin/lib/weakest-link.mjs +13 -0
  113. package/bin/lib/write-path-capabilities.mjs +2 -2
  114. package/dist/chunk-G3JRBJGH.js +16 -0
  115. package/dist/chunk-R3AJ5RSZ.cjs +16 -0
  116. package/dist/{configTypes-BJbaeoj4.d.cts → configTypes-GPvvdleM.d.cts} +12 -1
  117. package/dist/{configTypes-BJbaeoj4.d.ts → configTypes-GPvvdleM.d.ts} +12 -1
  118. package/dist/eslint/index.cjs +11 -11
  119. package/dist/eslint/index.d.cts +10 -1
  120. package/dist/eslint/index.d.ts +10 -1
  121. package/dist/eslint/index.js +11 -11
  122. package/dist/index.cjs +34 -34
  123. package/dist/index.d.cts +587 -515
  124. package/dist/index.d.ts +587 -515
  125. package/dist/index.js +34 -34
  126. package/dist/nestjs/index.cjs +1 -1
  127. package/dist/nestjs/index.d.cts +1 -1
  128. package/dist/nestjs/index.d.ts +1 -1
  129. package/dist/nestjs/index.js +1 -1
  130. package/dist/order/index.cjs +1 -1
  131. package/dist/order/index.js +1 -1
  132. package/dist/runtime/index.cjs +12 -12
  133. package/dist/runtime/index.d.cts +174 -136
  134. package/dist/runtime/index.d.ts +174 -136
  135. package/dist/runtime/index.js +12 -12
  136. package/dist/{types-DRinG-vT.d.cts → types-CnauMQQL.d.cts} +33 -25
  137. package/dist/{types-DRinG-vT.d.ts → types-CnauMQQL.d.ts} +33 -25
  138. package/docs/README.md +3 -1
  139. package/docs/agent-guide.md +140 -6
  140. package/docs/ai-gates.md +15 -1
  141. package/docs/configuration.md +40 -11
  142. package/docs/diagnostics.md +119 -3
  143. package/docs/package-surface.md +4 -1
  144. package/docs/product-voice.md +7 -1
  145. package/package.json +11 -2
  146. package/schemas/ark.config.schema.json +31 -3
  147. package/schemas/ark.invariant-probe.schema.json +368 -0
  148. package/server.json +2 -2
  149. package/templates/agent-skills/README.md +1 -1
  150. package/templates/agent-skills/ark-adopt/SKILL.md +1 -0
  151. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  152. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  153. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  154. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  155. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  156. package/templates/skills/ark-adopt.md +1 -0
  157. package/templates/skills/ark-contract.md +17 -0
  158. package/templates/skills/ark-coverage.md +2 -0
  159. package/templates/skills/ark-explore.md +7 -0
  160. package/templates/skills/ark-fix.md +20 -0
  161. package/templates/skills/ark-place.md +8 -0
  162. package/bin/lib/ark-order-error.mjs +0 -18
  163. package/bin/lib/ark-order-invariants.mjs +0 -388
  164. package/bin/lib/stable-hash.mjs +0 -90
  165. package/dist/chunk-MBXCFLO4.js +0 -16
  166. package/dist/chunk-RNQHRDCQ.cjs +0 -16
@@ -16,6 +16,7 @@ import { effectiveAnalysisConfig } from './analysis-policy.mjs';
16
16
  import { resolveCandidateFacts } from './resolved-candidate-facts.mjs';
17
17
  import {
18
18
  arkRulesDriftWarnings,
19
+ arkRulesLoadFailed,
19
20
  loadEffectiveArkRulesFromDisk,
20
21
  } from './effective-contract-load.mjs';
21
22
  import {
@@ -31,6 +32,7 @@ import {
31
32
  } from './invariant-tests-path.mjs';
32
33
  import { loadArkRuleFileHints } from './arkrule-file-hints.mjs';
33
34
  import { collectGovernedFiles } from './scan-files.mjs';
35
+ import { projectImporterIndex } from './import-graph-projection.mjs';
34
36
 
35
37
  const HINT_CACHE_CAP = 16;
36
38
  /** Process-local hint map keyed by scoped path + content hash. Not a second engine. */
@@ -206,13 +208,7 @@ export function resolveArchitectureSnapshot({
206
208
  observeInput,
207
209
  });
208
210
  if (arkRulesLoad.errors.length > 0) {
209
- const message = arkRulesLoad.errors
210
- .map((issue) => `- ${issue.path}: ${issue.message}`)
211
- .join('\n');
212
- const err = new Error(`Invalid Effective Contract (${configPath}):\n${message}`);
213
- err.code = 'ARKRULES_LOAD_FAILED';
214
- err.issues = arkRulesLoad.errors;
215
- throw err;
211
+ throw arkRulesLoadFailed(configPath, arkRulesLoad.errors);
216
212
  }
217
213
  const loadedContract = loadContract(effectiveConfig, configPath, {
218
214
  arkRules: arkRulesLoad.arkRules,
@@ -296,8 +292,14 @@ export function resolveArchitectureSnapshot({
296
292
 
297
293
  /**
298
294
  * Full architecture scan for governed files.
299
- * @returns {{ violations: object[], warnings: object[] }}
295
+ *
296
+ * `graphProjection: true` (doctor / report) also returns `importGraph`, a compact
297
+ * importer index projected from the resolved facts before they are released
298
+ * (ADR 0037 D1). The verdict fields are the same object either way.
299
+ * @returns {{ violations: object[], warnings: object[], importGraph?: object }}
300
300
  */
301
301
  export function runArchitectureScan(options) {
302
- return resolveArchitectureSnapshot({ ...options, captureInputs: false }).result;
302
+ const snapshot = resolveArchitectureSnapshot({ ...options, captureInputs: false });
303
+ if (options?.graphProjection !== true) return snapshot.result;
304
+ return { ...snapshot.result, importGraph: projectImporterIndex(snapshot.facts) };
303
305
  }
@@ -8,7 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/ark-order-doctor.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkOrderRuleId, } from './extra-merge-teeth.mjs';
11
+ import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkOrderRuleId, mergePlanesArkRulesInput, projectExtraPlaneStatus, } from './extra-merge-teeth.mjs';
12
12
  export const ARK_ORDER_DOCTOR_SCHEMA_VERSION = '1.0';
13
13
  const RESIDUAL_RULE_CAP = 12;
14
14
  export const ARKORDER_ONE_BREATH = 'Layers stop a bad import. ArkOrder stops rewriting a big product choice — like the billing plan — as if it were a seat count. Change those choices through a valve, not a generic update.';
@@ -54,17 +54,7 @@ export function summarizeArkOrderSection(input = {}) {
54
54
  const residualCount = uniqueIds.length;
55
55
  const mergePlanes = composeMergePlanesHonesty({
56
56
  classification: input.classification,
57
- arkRules: {
58
- active: input.arkRules?.active === true,
59
- structureEnforced: input.arkRules?.structureEnforced,
60
- structureTotal: input.arkRules?.structureTotal,
61
- structureAdvisory: input.arkRules?.structureAdvisory,
62
- invariantEnforced: input.arkRules?.invariantEnforced,
63
- invariantTotal: input.arkRules?.invariantTotal,
64
- invariantAdvisory: input.arkRules?.invariantAdvisory,
65
- covered: input.arkRules?.covered,
66
- uncovered: input.arkRules?.uncovered,
67
- },
57
+ arkRules: mergePlanesArkRulesInput(input.arkRules),
68
58
  arkRun: {
69
59
  present: input.arkRun?.present === true,
70
60
  mode: input.arkRun?.mode ?? null,
@@ -110,22 +100,7 @@ export function summarizeArkOrderSection(input = {}) {
110
100
  }
111
101
  /** Thin status slice — counts only; residual null means unknown, not green. */
112
102
  export function projectStatusArkOrder(input = {}) {
113
- const present = input.present === true;
114
- const mode = closedMode(input.mode);
115
- const residualRaw = input.residual;
116
- let residual = null;
117
- if (typeof residualRaw === 'number' && Number.isFinite(residualRaw) && residualRaw >= 0) {
118
- residual = Math.floor(residualRaw);
119
- }
120
- if (!present)
121
- residual = residual == null ? 0 : residual;
122
- return {
123
- notAScore: true,
124
- present,
125
- mode: present ? mode : null,
126
- extraMergeTeeth: present && mode === 'enforced' && input.extraMergeTeeth === true,
127
- residual,
128
- };
103
+ return projectExtraPlaneStatus(input);
129
104
  }
130
105
  export function formatArkOrderDoctorLines(section) {
131
106
  if (!section || section.notAScore !== true)
@@ -9,105 +9,12 @@
9
9
  */
10
10
 
11
11
  import { XI_TTL_KEY_RE } from './ark-order-types.mjs';
12
+ import { globToRegExp } from '../ark-layer-match.mjs';
12
13
  import { sourceHasPersistenceWrite, sourceImportsPersistenceDriverText } from './persistence-write-hint.mjs';
13
- export const ARKORDER_PLANE_FACTORY = 'createOrderPlane';
14
- export const ARKORDER_FORBIDDEN_METHODS = ['update', 'patch', 'set', 'mutate'];
15
- /**
16
- * XIWRITE-001: same engine as `globToRegExp` in src/domain/layerMatch.ts.
17
- * Inlined so generate:cli-pure emits self-contained bin/lib/ark-order-facts.mjs /
18
- * ark-order-sensors.mjs
19
- * (layerMatch is derived to bin/ark-layer-match.mjs, not a bin/lib sibling).
20
- */
21
- const appliesToRegexpCache = new Map();
22
- function escapeAppliesToLiteral(ch) {
23
- return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
24
- }
25
- function normalizeAppliesToGlob(pattern) {
26
- let out = '';
27
- for (let i = 0; i < pattern.length; i += 1) {
28
- const c = pattern[i];
29
- if (c === '\\' && i + 1 < pattern.length) {
30
- const next = pattern[i + 1];
31
- if ('*?{}[],'.includes(next) || next === '\\') {
32
- out += '\\' + next;
33
- i += 1;
34
- continue;
35
- }
36
- out += '/';
37
- continue;
38
- }
39
- out += c;
40
- }
41
- return out;
42
- }
43
- function appliesToBracesBalanced(glob) {
44
- let depth = 0;
45
- for (let i = 0; i < glob.length; i += 1) {
46
- const c = glob[i];
47
- if (c === '\\') {
48
- i += 1;
49
- continue;
50
- }
51
- if (c === '{')
52
- depth += 1;
53
- else if (c === '}') {
54
- depth -= 1;
55
- if (depth < 0)
56
- return false;
57
- }
58
- }
59
- return depth === 0;
60
- }
14
+ const ARKORDER_PLANE_FACTORY = 'createOrderPlane';
15
+ /** XIWRITE-001 `appliesTo` globs use the layer-glob engine (`globToRegExp`). */
61
16
  export function arkOrderGlobToRegExp(pattern) {
62
- const cached = appliesToRegexpCache.get(pattern);
63
- if (cached)
64
- return cached;
65
- const glob = normalizeAppliesToGlob(pattern);
66
- const useBraces = appliesToBracesBalanced(glob);
67
- let out = '';
68
- let braceDepth = 0;
69
- for (let i = 0; i < glob.length; i += 1) {
70
- const c = glob[i];
71
- if (c === '\\' && i + 1 < glob.length) {
72
- out += escapeAppliesToLiteral(glob[i + 1]);
73
- i += 1;
74
- }
75
- else if (c === '*') {
76
- if (glob[i + 1] === '*') {
77
- if (glob[i + 2] === '/') {
78
- out += '(?:.*/)?';
79
- i += 2;
80
- }
81
- else {
82
- out += '.*';
83
- i += 1;
84
- }
85
- }
86
- else {
87
- out += '[^/]*';
88
- }
89
- }
90
- else if (c === '?') {
91
- out += '[^/]';
92
- }
93
- else if (c === '{' && useBraces) {
94
- out += '(?:';
95
- braceDepth += 1;
96
- }
97
- else if (c === '}' && useBraces && braceDepth > 0) {
98
- out += ')';
99
- braceDepth -= 1;
100
- }
101
- else if (c === ',' && useBraces && braceDepth > 0) {
102
- out += '|';
103
- }
104
- else {
105
- out += escapeAppliesToLiteral(c);
106
- }
107
- }
108
- const re = new RegExp(`^${out}$`);
109
- appliesToRegexpCache.set(pattern, re);
110
- return re;
17
+ return globToRegExp(pattern);
111
18
  }
112
19
  function escapeRegExp(value) {
113
20
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -195,7 +102,7 @@ const TRAILING_IDENTIFIER_RE = /([A-Za-z_$][\w$]*)\s*!?\s*\??\s*$/;
195
102
  /** Leading identifier inside a parenthesized receiver: `x as T`, `<T>x`, `x!`, `x`. */
196
103
  const WRAPPED_IDENTIFIER_RE = /^\s*(?:<[^<>]*>\s*)?([A-Za-z_$][\w$]*)\s*!?\s*(?:\b(?:as|satisfies)\b[\s\S]*)?$/;
197
104
  /** Identifiers bound to `createOrderPlane(...)` in this file. */
198
- export function arkOrderPlaneBindings(content) {
105
+ function arkOrderPlaneBindings(content) {
199
106
  const source = stripCommentsPreservingLines(content);
200
107
  const names = new Set();
201
108
  const re = new RegExp(PLANE_BINDING_RE.source, 'g');
@@ -260,7 +167,7 @@ function specifierResolvesToPlaneRoot(file, specifier, planeRoots) {
260
167
  * named import from a declared plane root. `isArkOrderFile` reports an `arkgate/order`
261
168
  * import, which admits the conventional names file-wide.
262
169
  */
263
- export function arkOrderPlaneReceivers(file, content, options = {}) {
170
+ function arkOrderPlaneReceivers(file, content, options = {}) {
264
171
  const source = stripCommentsPreservingLines(content);
265
172
  const names = new Set(arkOrderPlaneBindings(content));
266
173
  const annotated = new RegExp(ORDER_PLANE_ANNOTATION_RE.source, 'g');
@@ -11,6 +11,5 @@
11
11
  export const DEFAULT_MAX_XI_KEYS = 7;
12
12
  /** ξ keys that name freshness. Freshness is σ, never ξ (runtime and static sensor share this). */
13
13
  export const XI_TTL_KEY_RE = /^(ttl|freshUntil|fresh_until|maxAge|max_age)$/i;
14
- export const INGEST_RESIDUAL_KINDS = ['absorb', 'escalate_up', 'hold'];
15
- export const INGEST_REASON_CODES = ['not-in-pattern', 'stale-sigma', 'pack', 'capacity'];
14
+ const INGEST_REASON_CODES = ['not-in-pattern', 'stale-sigma', 'pack', 'capacity'];
16
15
  export const CAPACITY_OPS = ['lte', 'lt', 'gte', 'gt'];
@@ -8,7 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/ark-run-doctor.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkRunRuleId, } from './extra-merge-teeth.mjs';
11
+ import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkRunRuleId, mergePlanesArkRulesInput, projectExtraPlaneStatus, } from './extra-merge-teeth.mjs';
12
12
  import { unresolvableLayerFlowLayers } from './source-policy.mjs';
13
13
  export const ARK_RUN_DOCTOR_SCHEMA_VERSION = '1.0';
14
14
  const RESIDUAL_RULE_CAP = 12;
@@ -59,17 +59,7 @@ export function summarizeArkRunSection(input = {}) {
59
59
  const residualCount = uniqueIds.length;
60
60
  const mergePlanes = composeMergePlanesHonesty({
61
61
  classification: input.classification,
62
- arkRules: {
63
- active: input.arkRules?.active === true,
64
- structureEnforced: input.arkRules?.structureEnforced,
65
- structureTotal: input.arkRules?.structureTotal,
66
- structureAdvisory: input.arkRules?.structureAdvisory,
67
- invariantEnforced: input.arkRules?.invariantEnforced,
68
- invariantTotal: input.arkRules?.invariantTotal,
69
- invariantAdvisory: input.arkRules?.invariantAdvisory,
70
- covered: input.arkRules?.covered,
71
- uncovered: input.arkRules?.uncovered,
72
- },
62
+ arkRules: mergePlanesArkRulesInput(input.arkRules),
73
63
  arkRun: {
74
64
  present: extra.present,
75
65
  mode: extra.mode,
@@ -112,22 +102,7 @@ export function summarizeArkRunSection(input = {}) {
112
102
  }
113
103
  /** Thin status slice — counts only; residual null means unknown, not green. */
114
104
  export function projectStatusArkRun(input = {}) {
115
- const present = input.present === true;
116
- const mode = closedMode(input.mode);
117
- const residualRaw = input.residual;
118
- let residual = null;
119
- if (typeof residualRaw === 'number' && Number.isFinite(residualRaw) && residualRaw >= 0) {
120
- residual = Math.floor(residualRaw);
121
- }
122
- if (!present)
123
- residual = residual == null ? 0 : residual;
124
- return {
125
- notAScore: true,
126
- present,
127
- mode: present ? mode : null,
128
- extraMergeTeeth: present && mode === 'enforced' && input.extraMergeTeeth === true,
129
- residual,
130
- };
105
+ return projectExtraPlaneStatus(input);
131
106
  }
132
107
  export function formatArkRunDoctorLines(section) {
133
108
  if (!section || section.notAScore !== true)
@@ -23,14 +23,14 @@ export const ARKRUN_KERNEL_FACTORY_CALLEES = [
23
23
  * count as a kernel factory only when `ArkModule` is imported from a kernel module
24
24
  * (`arkgate/nestjs`). A bare `.forRoot(` on any other receiver never counts.
25
25
  */
26
- export const ARKRUN_NEST_KERNEL_MODULE = 'ArkModule';
27
- export const ARKRUN_NEST_FACTORY_METHODS = ['forRoot', 'forRootAsync'];
26
+ const ARKRUN_NEST_KERNEL_MODULE = 'ArkModule';
27
+ const ARKRUN_NEST_FACTORY_METHODS = ['forRoot', 'forRootAsync'];
28
28
  /**
29
29
  * Kernel types whose annotated bindings (`ark: ArkKernel`, constructor-injected
30
30
  * `private readonly ark: ArkKernel`) are traced as kernel receivers when the type
31
31
  * is imported from a kernel module.
32
32
  */
33
- export const ARKRUN_KERNEL_RECEIVER_TYPES = [
33
+ const ARKRUN_KERNEL_RECEIVER_TYPES = [
34
34
  'ArkKernel',
35
35
  'EventBus',
36
36
  'EventPublisher',
@@ -310,21 +310,33 @@ export function extractArkRunValueImportDependenciesFromSource(file, content) {
310
310
  }
311
311
  return out;
312
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
+ }
313
334
  /** PascalCase named bindings from value import clauses (snippet admitted constructors). */
314
335
  export function extractArkRunImportedConstructorNamesFromSource(content) {
315
336
  const names = [];
316
337
  forEachArkRunValueImportClause(content, (clause) => {
317
- const braced = /\{([^}]*)\}/.exec(clause);
318
- if (!braced?.[1])
319
- return;
320
- for (const part of braced[1].split(',')) {
321
- const piece = part.trim();
322
- if (!piece || piece.startsWith('type '))
323
- continue;
324
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
325
- const local = alias?.[2] ?? /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece)?.[1];
326
- const original = alias?.[1] ?? local;
327
- if (local && original && /^[A-Z]/.test(original))
338
+ for (const { local, original } of bracedValueImportBindings(clause)) {
339
+ if (/^[A-Z]/.test(original))
328
340
  names.push(local, original);
329
341
  }
330
342
  });
@@ -342,21 +354,8 @@ function collectKernelImportBindings(content) {
342
354
  const defaultIdent = /^([A-Za-z_][A-Za-z0-9_]*)\s*(?:,|$)/.exec(clause.trim());
343
355
  if (defaultIdent?.[1])
344
356
  named.set(defaultIdent[1], defaultIdent[1]);
345
- const braced = /\{([^}]*)\}/.exec(clause);
346
- if (!braced?.[1])
347
- return;
348
- for (const part of braced[1].split(',')) {
349
- const piece = part.trim();
350
- if (!piece || piece.startsWith('type '))
351
- continue;
352
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
353
- if (alias) {
354
- named.set(alias[2], alias[1]);
355
- continue;
356
- }
357
- const ident = /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
358
- if (ident?.[1])
359
- named.set(ident[1], ident[1]);
357
+ for (const { local, original } of bracedValueImportBindings(clause)) {
358
+ named.set(local, original);
360
359
  }
361
360
  });
362
361
  return { named, namespaces };
@@ -364,17 +363,8 @@ function collectKernelImportBindings(content) {
364
363
  function collectImportedConstructors(content, admitted) {
365
364
  const out = new Set(admitted);
366
365
  parseValueImportClause(content, (clause, specifier) => {
367
- const braced = /\{([^}]*)\}/.exec(clause);
368
- if (!braced?.[1])
369
- return;
370
- for (const part of braced[1].split(',')) {
371
- const piece = part.trim();
372
- if (!piece || piece.startsWith('type '))
373
- continue;
374
- const alias = /^([A-Za-z_][A-Za-z0-9_]*)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece);
375
- const local = alias?.[2] ?? /^([A-Za-z_][A-Za-z0-9_]*)$/.exec(piece)?.[1];
376
- const original = alias?.[1] ?? local;
377
- if (!local || !original || !/^[A-Z]/.test(original))
366
+ for (const { local, original } of bracedValueImportBindings(clause)) {
367
+ if (!/^[A-Z]/.test(original))
378
368
  continue;
379
369
  if (isArkRunKernelModuleSpecifier(specifier) || admitted.has(original) || admitted.has(local)) {
380
370
  out.add(local);
@@ -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 = {
@@ -11,11 +11,12 @@
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
13
  import { buildArkRuleFileHints } from './arkrules-sensors.mjs';
14
+ import { matchSimpleGlob } from './invariant-coverage-io.mjs';
14
15
 
15
16
  /** Default hint-file budget. Same lever as `coverage.maxFiles`. */
16
17
  export const DEFAULT_MAX_HINT_FILES = 400;
17
18
  /** Hard ceiling — same clamp as coverage.maxFiles. */
18
- export const MAX_HINT_FILES_CAP = 20_000;
19
+ const MAX_HINT_FILES_CAP = 20_000;
19
20
  const MAX_FILE_BYTES = 256 * 1024;
20
21
 
21
22
  const HINT_SENSORS = new Set([
@@ -37,17 +38,6 @@ export function needsArkRuleFileHints(arkRules) {
37
38
  return (arkRules?.structure ?? []).some((rule) => HINT_SENSORS.has(rule?.sensor));
38
39
  }
39
40
 
40
- /**
41
- * @param {unknown} maxFiles
42
- * @returns {number}
43
- */
44
- export function resolveHintBudget(maxFiles) {
45
- if (Number.isInteger(maxFiles) && maxFiles > 0) {
46
- return Math.min(maxFiles, MAX_HINT_FILES_CAP);
47
- }
48
- return DEFAULT_MAX_HINT_FILES;
49
- }
50
-
51
41
  /**
52
42
  * @param {unknown} hints
53
43
  * @returns {null | {
@@ -74,41 +64,6 @@ export function formatHintBudgetDoctorLine(budget) {
74
64
  return `${HINT_BUDGET_DOCTOR_LINE} Hinted ${budget.hinted} of ${budget.governed} eligible governed files (budget ${budget.budget}).`;
75
65
  }
76
66
 
77
- /**
78
- * Minimal glob match (double-star slash = zero path segments).
79
- * @param {string} glob
80
- * @param {string} file
81
- */
82
- function matchSimpleGlob(glob, file) {
83
- const pattern = String(glob || '').replace(/\\/g, '/');
84
- const target = String(file || '').replace(/\\/g, '/');
85
- if (!pattern) return false;
86
- let out = '';
87
- for (let i = 0; i < pattern.length; i += 1) {
88
- const c = pattern[i];
89
- if (c === '*') {
90
- if (pattern[i + 1] === '*') {
91
- if (pattern[i + 2] === '/') {
92
- out += '(?:.*/)?';
93
- i += 2;
94
- } else {
95
- out += '.*';
96
- i += 1;
97
- }
98
- } else {
99
- out += '[^/]*';
100
- }
101
- } else if (c === '?') {
102
- out += '[^/]';
103
- } else if (/[.+^${}()|[\]\\]/.test(c)) {
104
- out += `\\${c}`;
105
- } else {
106
- out += c;
107
- }
108
- }
109
- return new RegExp(`^${out}$`).test(target);
110
- }
111
-
112
67
  /**
113
68
  * @param {string} file
114
69
  * @param {unknown} appliesTo