arkgate 4.8.19 → 4.8.21

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 (85) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +107 -117
  3. package/bin/ark-check-runtime.mjs +26 -4
  4. package/bin/ark-layer-match.mjs +236 -12
  5. package/bin/ark.mjs +6 -2
  6. package/bin/lib/analysis-engine.mjs +13 -6
  7. package/bin/lib/config-contract.mjs +6 -0
  8. package/bin/lib/diagnostic-catalog.mjs +1 -0
  9. package/bin/lib/doctor-advisories.mjs +22 -1
  10. package/bin/lib/doctor-human.mjs +11 -9
  11. package/bin/lib/doctor-plan.mjs +17 -14
  12. package/bin/lib/enforcement-honesty.mjs +30 -2
  13. package/bin/lib/first-run-help.mjs +7 -1
  14. package/bin/lib/html-report-advisories.mjs +8 -0
  15. package/bin/lib/html-report-depth.mjs +12 -7
  16. package/bin/lib/invariant-coverage.mjs +406 -54
  17. package/bin/lib/managed-upgrade.mjs +59 -14
  18. package/bin/lib/package-manager.mjs +5 -2
  19. package/bin/lib/physical-cohesion.mjs +68 -51
  20. package/bin/lib/pilot-loop.mjs +183 -37
  21. package/bin/lib/reshape-decisions.mjs +2 -2
  22. package/bin/lib/rules-under-contract.mjs +40 -4
  23. package/bin/lib/start-install-recovery.mjs +156 -0
  24. package/bin/lib/start-preview.mjs +0 -54
  25. package/bin/lib/upgrade-behavior-changes.mjs +83 -0
  26. package/bin/lib/upgrade-command.mjs +69 -31
  27. package/bin/lib/upgrade-outcome.mjs +359 -0
  28. package/bin/lib/violations.mjs +109 -0
  29. package/dist/{configTypes-VD0qcubY.d.ts → configTypes-CTELo87-.d.ts} +9 -0
  30. package/dist/{diagnosticCatalog-D2v0ASN2.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +79 -5
  31. package/dist/eslint/index.cjs +4 -4
  32. package/dist/eslint/index.d.ts +35 -4
  33. package/dist/eslint/index.js +4 -4
  34. package/dist/index.cjs +25 -18
  35. package/dist/index.d.ts +13 -4
  36. package/dist/index.js +27 -20
  37. package/dist/nestjs/index.cjs +9 -9
  38. package/dist/nestjs/index.d.ts +3 -3
  39. package/dist/nestjs/index.js +9 -9
  40. package/dist/runtime/index.cjs +18 -11
  41. package/dist/runtime/index.d.ts +6 -6
  42. package/dist/runtime/index.js +18 -11
  43. package/dist/{types-D5GT5ZT8.d.ts → types-fQ9-KrBj.d.ts} +1 -1
  44. package/dist/{types-BSzRy2X1.d.ts → types-yDNEQ603.d.ts} +1 -1
  45. package/docs/README.md +4 -3
  46. package/docs/agent-guide.md +15 -7
  47. package/docs/brownfield-adoption.md +4 -3
  48. package/docs/configuration.md +46 -8
  49. package/docs/develop.md +1 -1
  50. package/docs/diagnostics.md +12 -2
  51. package/docs/package-surface.md +4 -3
  52. package/docs/product-voice.md +11 -3
  53. package/docs/use.md +1 -1
  54. package/package.json +3 -3
  55. package/schemas/ark.config.schema.json +14 -0
  56. package/server.json +2 -2
  57. package/templates/agent-skills/README.md +1 -1
  58. package/templates/agent-skills/ark-adopt/SKILL.md +18 -1
  59. package/templates/agent-skills/ark-architect/SKILL.md +2 -0
  60. package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
  61. package/templates/agent-skills/ark-contract/SKILL.md +2 -0
  62. package/templates/agent-skills/ark-coverage/SKILL.md +3 -1
  63. package/templates/agent-skills/ark-explain/SKILL.md +3 -1
  64. package/templates/agent-skills/ark-explore/SKILL.md +15 -1
  65. package/templates/agent-skills/ark-fix/SKILL.md +2 -0
  66. package/templates/agent-skills/ark-loop/SKILL.md +2 -0
  67. package/templates/agent-skills/ark-order/SKILL.md +2 -0
  68. package/templates/agent-skills/ark-place/SKILL.md +2 -0
  69. package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
  70. package/templates/agent-skills/ark-think/SKILL.md +2 -0
  71. package/templates/agent-skills/ark-upgrade/SKILL.md +25 -10
  72. package/templates/skills/ark-adopt.md +18 -1
  73. package/templates/skills/ark-architect.md +2 -0
  74. package/templates/skills/ark-autopilot.md +6 -2
  75. package/templates/skills/ark-contract.md +2 -0
  76. package/templates/skills/ark-coverage.md +3 -1
  77. package/templates/skills/ark-explain.md +3 -1
  78. package/templates/skills/ark-explore.md +15 -1
  79. package/templates/skills/ark-fix.md +2 -0
  80. package/templates/skills/ark-loop.md +2 -0
  81. package/templates/skills/ark-order.md +2 -0
  82. package/templates/skills/ark-place.md +2 -0
  83. package/templates/skills/ark-runtime.md +2 -0
  84. package/templates/skills/ark-think.md +2 -0
  85. package/templates/skills/ark-upgrade.md +25 -10
@@ -0,0 +1,359 @@
1
+ /**
2
+ * UpgradeOutcome — one value for `upgrade --apply`.
3
+ *
4
+ * Sketch (D2). Callers see one object and one exit mapping. Gitignore
5
+ * detection, rule aggregation, and version-note lookup stay in here.
6
+ *
7
+ * classifyManagedAsset({ ..., gitignored }) →
8
+ * { state: 'absent-local', requiresConsent: false } when recorded + gitignored + missing
9
+ *
10
+ * runPostUpgradeVerification(root) →
11
+ * { verdict: 'green'|'red'|'skipped', failing: [{ ruleId, count, sample }], behaviorChanges: string[] }
12
+ *
13
+ * exitCodeFor(outcome) →
14
+ * 0 applied+green (also nothing-to-apply / verification skipped)
15
+ * 3 applied+red
16
+ * 2 blocked
17
+ * 1 error
18
+ *
19
+ * JSON fields on the existing upgrade payload are additive (`outcome`, `postUpgrade`,
20
+ * per-asset `reason` / `nextCommand`, state `absent-local`).
21
+ */
22
+ import { spawnSync } from 'node:child_process';
23
+
24
+ import { behaviorChangeLines } from './upgrade-behavior-changes.mjs';
25
+
26
+ /**
27
+ * @typedef {'applied-green'|'applied-skipped'|'applied-red'|'blocked'|'error'|'nothing'|'preview'} UpgradeOutcomeKind
28
+ * @typedef {{ ruleId: string, count: number, sample: string }} FailingRule
29
+ * @typedef {{
30
+ * verdict: 'green'|'red'|'skipped',
31
+ * failing: FailingRule[],
32
+ * behaviorChanges: string[],
33
+ * mode: 'strict-merge'|'skipped',
34
+ * exitCode: number,
35
+ * stderr?: string,
36
+ * }} PostUpgradeVerification
37
+ * @typedef {{
38
+ * kind: UpgradeOutcomeKind,
39
+ * applied: boolean,
40
+ * blocked: boolean,
41
+ * postUpgrade: PostUpgradeVerification|null,
42
+ * error?: string,
43
+ * }} UpgradeOutcome
44
+ */
45
+
46
+ export const ABSENT_LOCAL_REASON =
47
+ 'Recorded managed file is missing and gitignored (per-machine file). Recreated without consent.';
48
+
49
+ /**
50
+ * @param {string} state
51
+ * @returns {string}
52
+ */
53
+ export function blockedAssetReason(state) {
54
+ if (state === 'missing') {
55
+ return 'Recorded managed file is missing and is not gitignored. Apply will not recreate a deleted file without consent.';
56
+ }
57
+ if (state === 'conflicted') {
58
+ return 'Managed base and local edits both changed. Apply will not overwrite without consent.';
59
+ }
60
+ return 'Apply needs consent before changing this managed file.';
61
+ }
62
+
63
+ /**
64
+ * Paths git would ignore. Not a git repo, or git missing → empty set
65
+ * (fail closed toward consent). Global excludes are not consulted.
66
+ *
67
+ * @param {string} root
68
+ * @param {string[]} relativePaths
69
+ * @returns {Set<string>}
70
+ */
71
+ export function gitignoredPathSet(root, relativePaths) {
72
+ const paths = [...new Set(relativePaths.filter((entry) => typeof entry === 'string' && entry.length > 0))];
73
+ if (paths.length === 0) return new Set();
74
+ let result;
75
+ try {
76
+ result = spawnSync(
77
+ 'git',
78
+ ['-c', 'core.excludesFile=/dev/null', 'check-ignore', '-z', '--stdin'],
79
+ {
80
+ cwd: root,
81
+ input: `${paths.join('\0')}\0`,
82
+ encoding: 'utf8',
83
+ timeout: 5000,
84
+ env: {
85
+ ...process.env,
86
+ GIT_CONFIG_NOSYSTEM: '1',
87
+ GIT_CONFIG_GLOBAL: '/dev/null',
88
+ },
89
+ }
90
+ );
91
+ } catch {
92
+ return new Set();
93
+ }
94
+ if (!result || result.error || (result.status !== 0 && result.status !== 1)) return new Set();
95
+ const ignored = new Set();
96
+ for (const entry of String(result.stdout ?? '').split('\0')) {
97
+ if (entry) ignored.add(entry);
98
+ }
99
+ return ignored;
100
+ }
101
+
102
+ /**
103
+ * @param {unknown} check
104
+ * @returns {FailingRule[]}
105
+ */
106
+ export function aggregateFailingRules(check) {
107
+ const violations = Array.isArray(check?.violations) ? check.violations : [];
108
+ /** @type {Map<string, FailingRule>} */
109
+ const groups = new Map();
110
+ for (const violation of violations) {
111
+ if (!violation || typeof violation !== 'object') continue;
112
+ if (violation.failsStrict === false) continue;
113
+ const ruleId = typeof violation.ruleId === 'string' && violation.ruleId.trim()
114
+ ? violation.ruleId.trim()
115
+ : 'UNKNOWN';
116
+ const existing = groups.get(ruleId) ?? { ruleId, count: 0, sample: '' };
117
+ existing.count += 1;
118
+ if (!existing.sample) existing.sample = sampleForViolation(violation, ruleId);
119
+ groups.set(ruleId, existing);
120
+ }
121
+ return [...groups.values()].sort(
122
+ (left, right) => right.count - left.count || left.ruleId.localeCompare(right.ruleId)
123
+ );
124
+ }
125
+
126
+ /**
127
+ * @param {Record<string, unknown>} violation
128
+ * @param {string} ruleId
129
+ */
130
+ function sampleForViolation(violation, ruleId) {
131
+ const file = typeof violation.file === 'string'
132
+ ? violation.file
133
+ : typeof violation.path === 'string'
134
+ ? violation.path
135
+ : '';
136
+ const line = Number.isInteger(violation.line) ? `:${violation.line}` : '';
137
+ const where = file ? `${file}${line}` : '';
138
+ const message = typeof violation.message === 'string' ? violation.message.trim() : '';
139
+ const sample = [where, message].filter(Boolean).join(' — ') || ruleId;
140
+ return sample.length > 180 ? `${sample.slice(0, 177)}...` : sample;
141
+ }
142
+
143
+ /**
144
+ * @param {string} stdout
145
+ * @returns {Record<string, unknown>|null}
146
+ */
147
+ export function parseCheckStdout(stdout) {
148
+ const text = typeof stdout === 'string' ? stdout.trim() : '';
149
+ if (!text) return null;
150
+ try {
151
+ const parsed = JSON.parse(text);
152
+ return parsed && typeof parsed === 'object' ? parsed : null;
153
+ } catch {
154
+ const start = text.indexOf('{');
155
+ const end = text.lastIndexOf('}');
156
+ if (start === -1 || end <= start) return null;
157
+ try {
158
+ const parsed = JSON.parse(text.slice(start, end + 1));
159
+ return parsed && typeof parsed === 'object' ? parsed : null;
160
+ } catch {
161
+ return null;
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @param {string} root
168
+ * @param {string} arkCheck
169
+ * @returns {{ exitCode: number, stdout: string, stderr: string }}
170
+ */
171
+ function spawnArchitectureCheck(root, arkCheck) {
172
+ if (typeof arkCheck !== 'string' || !arkCheck) {
173
+ return { exitCode: 1, stdout: '', stderr: 'architecture check binary is missing' };
174
+ }
175
+ const result = spawnSync(
176
+ process.execPath,
177
+ [arkCheck, '--root', root, '--config', 'ark.config.json', '--strict-merge', '--json'],
178
+ {
179
+ cwd: root,
180
+ stdio: ['ignore', 'pipe', 'pipe'],
181
+ encoding: 'utf8',
182
+ }
183
+ );
184
+ return {
185
+ exitCode: result.status ?? 1,
186
+ stdout: result.stdout ?? '',
187
+ stderr: result.stderr ?? '',
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Structured post-upgrade verification. Reuses check JSON (rule ids), and
193
+ * attaches behavior-change lines from the shipped per-version table.
194
+ *
195
+ * @param {string} root
196
+ * @param {{
197
+ * strict?: boolean,
198
+ * arkCheck?: string,
199
+ * packageVersion?: string,
200
+ * checkResult?: { exitCode?: number, status?: number, stdout?: string, stderr?: string },
201
+ * }} [options]
202
+ * @returns {PostUpgradeVerification}
203
+ */
204
+ export function runPostUpgradeVerification(root, options = {}) {
205
+ const behaviorChanges = behaviorChangeLines();
206
+ if (options.strict === false) {
207
+ return {
208
+ verdict: 'skipped',
209
+ failing: [],
210
+ behaviorChanges,
211
+ mode: 'skipped',
212
+ exitCode: 0,
213
+ };
214
+ }
215
+ const raw = options.checkResult ?? spawnArchitectureCheck(root, options.arkCheck);
216
+ const parsed = parseCheckStdout(raw.stdout ?? '');
217
+ const failing = aggregateFailingRules(parsed);
218
+ const exitCode = raw.exitCode ?? raw.status ?? 1;
219
+ const red = exitCode !== 0 || parsed?.ok === false || parsed?.valid === false;
220
+ if (red && failing.length === 0) {
221
+ const sampleSource = String(raw.stderr || 'architecture check failed').trim() || 'architecture check failed';
222
+ failing.push({
223
+ ruleId: 'ARCHITECTURE_CHECK',
224
+ count: 1,
225
+ sample: sampleSource.length > 180 ? `${sampleSource.slice(0, 177)}...` : sampleSource,
226
+ });
227
+ }
228
+ const stderr = typeof raw.stderr === 'string' && raw.stderr.trim() ? raw.stderr.trim() : undefined;
229
+ return {
230
+ verdict: red ? 'red' : 'green',
231
+ failing: red ? failing : [],
232
+ behaviorChanges,
233
+ mode: 'strict-merge',
234
+ exitCode: red ? (exitCode || 1) : 0,
235
+ ...(stderr ? { stderr } : {}),
236
+ };
237
+ }
238
+
239
+ /**
240
+ * @param {{
241
+ * applied?: boolean,
242
+ * blocked?: boolean,
243
+ * nothingToApply?: boolean,
244
+ * postUpgrade?: PostUpgradeVerification|null,
245
+ * error?: unknown,
246
+ * }} input
247
+ * @returns {UpgradeOutcome}
248
+ */
249
+ export function upgradeOutcome(input) {
250
+ if (input.error != null && input.error !== false) {
251
+ const message = input.error instanceof Error ? input.error.message : String(input.error);
252
+ return { kind: 'error', applied: false, blocked: false, postUpgrade: null, error: message };
253
+ }
254
+ if (input.blocked) {
255
+ return { kind: 'blocked', applied: false, blocked: true, postUpgrade: null };
256
+ }
257
+ if (input.nothingToApply && !input.applied) {
258
+ return { kind: 'nothing', applied: false, blocked: false, postUpgrade: null };
259
+ }
260
+ const postUpgrade = input.postUpgrade ?? null;
261
+ if (postUpgrade?.verdict === 'red') {
262
+ return { kind: 'applied-red', applied: true, blocked: false, postUpgrade };
263
+ }
264
+ if (postUpgrade?.verdict === 'skipped') {
265
+ return { kind: 'applied-skipped', applied: Boolean(input.applied), blocked: false, postUpgrade };
266
+ }
267
+ return { kind: 'applied-green', applied: Boolean(input.applied), blocked: false, postUpgrade };
268
+ }
269
+
270
+ /**
271
+ * @param {UpgradeOutcome|null|undefined} outcome
272
+ * @returns {0|1|2|3}
273
+ */
274
+ export function exitCodeFor(outcome) {
275
+ switch (outcome?.kind) {
276
+ case 'applied-green':
277
+ case 'applied-skipped':
278
+ case 'nothing':
279
+ case 'preview':
280
+ return 0;
281
+ case 'applied-red':
282
+ return 3;
283
+ case 'blocked':
284
+ return 2;
285
+ case 'error':
286
+ return 1;
287
+ default:
288
+ return 1;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * @param {{ mode?: string, exitCode?: number, verdict?: string }} [verification]
294
+ * @returns {boolean|null}
295
+ */
296
+ export function architectureVerificationOk(verification) {
297
+ if (!verification || verification.mode === 'skipped' || verification.verdict === 'skipped') return null;
298
+ if (verification.verdict === 'green') return true;
299
+ if (verification.verdict === 'red') return false;
300
+ if (verification.exitCode === 0) return true;
301
+ if (typeof verification.exitCode === 'number') return false;
302
+ return null;
303
+ }
304
+
305
+ /**
306
+ * @param {{ mode?: string, exitCode?: number, verdict?: string, failing?: FailingRule[] }} [verification]
307
+ * @returns {string}
308
+ */
309
+ export function architectureVerificationDetail(verification) {
310
+ if (!verification || verification.mode === 'skipped' || verification.verdict === 'skipped') {
311
+ return 'Strict architecture verification was skipped (--no-strict).';
312
+ }
313
+ const code = verification.exitCode;
314
+ if (architectureVerificationOk(verification) === true) {
315
+ return 'Strict-merge architecture verification passed.';
316
+ }
317
+ const failing = Array.isArray(verification.failing) ? verification.failing : [];
318
+ const rules = failing.map((row) => `${row.ruleId} ×${row.count}`).join(', ');
319
+ const head = `Architecture verification exit ${code ?? 'unknown'}.`;
320
+ if (!rules) return head;
321
+ return `${head} Failing rules: ${rules}. Read behaviorChanges before editing code.`;
322
+ }
323
+
324
+ /**
325
+ * @param {{ assets?: Array<{ blocked?: boolean, path?: string, state?: string, reason?: string, nextCommand?: string }>, blocked?: boolean, nextCommand?: string }} plan
326
+ * @param {{ refused?: boolean }} [options]
327
+ * @returns {string[]}
328
+ */
329
+ export function formatBlockedAssets(plan, options = {}) {
330
+ const blocked = (plan?.assets ?? []).filter((asset) => asset?.blocked);
331
+ if (blocked.length === 0) return [];
332
+ const lines = [options.refused ? 'Apply refused.' : 'Blocked until you consent:'];
333
+ for (const asset of blocked) {
334
+ const reason = asset.reason || blockedAssetReason(asset.state ?? '');
335
+ lines.push(` ${asset.path} — ${reason}`);
336
+ }
337
+ const next = blocked.find((asset) => asset.nextCommand)?.nextCommand ?? plan?.nextCommand;
338
+ if (next) lines.push(`Next: ${next}`);
339
+ return lines;
340
+ }
341
+
342
+ /**
343
+ * @param {PostUpgradeVerification|null|undefined} postUpgrade
344
+ * @returns {string[]}
345
+ */
346
+ export function formatPostUpgradeHuman(postUpgrade) {
347
+ if (!postUpgrade) return [];
348
+ const lines = [`Post-upgrade verification: ${postUpgrade.verdict}.`];
349
+ if (postUpgrade.verdict !== 'red') return lines;
350
+ lines.push('Failing rules:');
351
+ const failing = postUpgrade.failing ?? [];
352
+ if (failing.length === 0) lines.push(' (check returned red without a rule id)');
353
+ for (const row of failing) {
354
+ lines.push(` ${row.ruleId} ×${row.count} — ${row.sample}`);
355
+ }
356
+ lines.push('Read behaviorChanges before editing code.');
357
+ for (const note of postUpgrade.behaviorChanges ?? []) lines.push(` ${note}`);
358
+ return lines;
359
+ }
@@ -97,6 +97,115 @@ export function printWarning(warning) {
97
97
  );
98
98
  }
99
99
 
100
+ /** Advisory the engine emits once per shared-root → slice edge. Humans see a group. */
101
+ export const SHARED_IMPORTS_SLICE_RULE_ID = 'SHARED_IMPORTS_SLICE';
102
+
103
+ /**
104
+ * Where the full per-edge list lives. Human check output names this on every
105
+ * grouped line. `--json` is the machine list; `--doctor` prints every edge.
106
+ */
107
+ export const SHARED_IMPORTS_SLICE_FULL_LIST =
108
+ 'Full list: ark-check --json or ark-check --doctor';
109
+
110
+ const SHARED_IMPORTS_SLICE_EXAMPLE_LIMIT = 3;
111
+
112
+ function formatAdvisoryCount(count) {
113
+ return String(count).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
114
+ }
115
+
116
+ function layerLabel(value) {
117
+ return typeof value === 'string' && value.trim() ? value.trim() : '?';
118
+ }
119
+
120
+ /** Group key: one fact per rule and layer edge. */
121
+ export function sharedImportsSliceGroupKey(warning) {
122
+ return [
123
+ typeof warning?.ruleId === 'string' ? warning.ruleId : SHARED_IMPORTS_SLICE_RULE_ID,
124
+ layerLabel(warning?.fromLayer),
125
+ layerLabel(warning?.toLayer),
126
+ ].join('\0');
127
+ }
128
+
129
+ function formatSharedImportsSliceExample(warning) {
130
+ const file = typeof warning?.file === 'string' && warning.file.trim() ? warning.file.trim() : 'unknown';
131
+ const line = Number.isInteger(warning?.line) && warning.line > 0 ? warning.line : 1;
132
+ const slice =
133
+ typeof warning?.toSlice === 'string' && warning.toSlice.trim() ? warning.toSlice.trim() : 'slice';
134
+ const target =
135
+ typeof warning?.target === 'string' && warning.target.trim() ? ` (${warning.target.trim()})` : '';
136
+ return `${file}:${line} → ${slice}${target}`;
137
+ }
138
+
139
+ /** One human line for a (ruleId, fromLayer → toLayer) group. Count plus top 3 edges. */
140
+ export function formatSharedImportsSliceSummary(group) {
141
+ const sample = group[0] ?? {};
142
+ const ruleId =
143
+ typeof sample.ruleId === 'string' ? sample.ruleId : SHARED_IMPORTS_SLICE_RULE_ID;
144
+ const count = group.length;
145
+ const noun = count === 1 ? 'bridge' : 'bridges';
146
+ const examples = group
147
+ .slice(0, SHARED_IMPORTS_SLICE_EXAMPLE_LIMIT)
148
+ .map(formatSharedImportsSliceExample)
149
+ .join('; ');
150
+ return (
151
+ `warning ${ruleId} ${layerLabel(sample.fromLayer)} → ${layerLabel(sample.toLayer)}: ` +
152
+ `${formatAdvisoryCount(count)} shared-root → slice ${noun} (e.g. ${examples}). ` +
153
+ `The wall is direct-only. ${SHARED_IMPORTS_SLICE_FULL_LIST}`
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Human check lines. SHARED_IMPORTS_SLICE collapses to one line per group.
159
+ * Every other warning stays one line. Does not mutate `warnings` — `--json`
160
+ * and doctor still walk the per-edge array.
161
+ */
162
+ export function humanWarningLines(warnings) {
163
+ const groups = new Map();
164
+ const slots = [];
165
+ for (const warning of warnings ?? []) {
166
+ if (warning?.ruleId !== SHARED_IMPORTS_SLICE_RULE_ID) {
167
+ slots.push({ kind: 'line', line: formatWarningLine(warning) });
168
+ continue;
169
+ }
170
+ const key = sharedImportsSliceGroupKey(warning);
171
+ let group = groups.get(key);
172
+ if (!group) {
173
+ group = [];
174
+ groups.set(key, group);
175
+ slots.push({ kind: 'group', key });
176
+ }
177
+ group.push(warning);
178
+ }
179
+ return slots.map((slot) =>
180
+ slot.kind === 'line' ? slot.line : formatSharedImportsSliceSummary(groups.get(slot.key))
181
+ );
182
+ }
183
+
184
+ function printColoredWarningLine(line) {
185
+ const painted = line.startsWith('warning ')
186
+ ? `${color.yellow('warning')}${line.slice('warning'.length)}`
187
+ : line;
188
+ console.error(painted);
189
+ }
190
+
191
+ /** ark-check human renderer. Grouping stops here; the warnings array is unchanged. */
192
+ export function printHumanWarnings(warnings) {
193
+ for (const line of humanWarningLines(warnings)) {
194
+ printColoredWarningLine(line);
195
+ }
196
+ }
197
+
198
+ /** Doctor human list: every shared-root → slice edge, not the grouped summary. */
199
+ export function printSharedImportsSliceBridgeList(warnings) {
200
+ const lines = (warnings ?? [])
201
+ .filter((warning) => warning?.ruleId === SHARED_IMPORTS_SLICE_RULE_ID)
202
+ .map((warning) => formatWarningLine(warning));
203
+ if (lines.length === 0) return;
204
+ console.log('');
205
+ console.log(`Shared-root → slice bridges (${formatAdvisoryCount(lines.length)}):`);
206
+ for (const line of lines) console.log(line);
207
+ }
208
+
100
209
  /** Non-blocking type-only placement debt: exit stays 0; do not paint ✖. */
101
210
  export function isNonBlockingPlacementDebt(violation) {
102
211
  return violation?.failsStrict === false;
@@ -49,6 +49,8 @@ type ArkConfigLayer = {
49
49
  /** Alias of reserved — empty pattern matches are allowed. */
50
50
  allowEmpty?: boolean;
51
51
  };
52
+ /** How a starred `sliceFolders` prefix is named. Absent equals `path`. */
53
+ type ArkConfigSliceIdentity = 'path' | 'stars';
52
54
  type ArkConfigRule = {
53
55
  from: string;
54
56
  to: string;
@@ -56,10 +58,17 @@ type ArkConfigRule = {
56
58
  message?: string;
57
59
  peerIsolation?: boolean;
58
60
  sliceFolders?: string[];
61
+ /**
62
+ * How a starred `sliceFolders` prefix is named.
63
+ * Absent and `path` keep today's ids. `stars` is the last literal plus the star bindings.
64
+ */
65
+ sliceIdentity?: ArkConfigSliceIdentity;
59
66
  /** Roots the repo declares shared on purpose — evidence, not unclassifiable. */
60
67
  sharedRoots?: string[];
61
68
  /** Directed slice→slice edges the repo declares on purpose. */
62
69
  allowedCrossSlice?: ArkConfigCrossSliceEdge[];
70
+ /** When `"deny"`, a declared shared root may not import a slice. */
71
+ sharedImportsSlice?: 'deny';
63
72
  };
64
73
  type ArkConfigCrossSliceEdge = {
65
74
  from: string;
@@ -1,5 +1,5 @@
1
- import { e as CreateArchitectureProfileOptions, b as ArchitectureProfile, d as ArkCheckConfig, C as CreateArchitectureProfileFromArkConfigOptions, f as CreateElevenLayerArkConfigOptions, i as Policy, j as IntentCreator, I as IntentName } from './types-D5GT5ZT8.js';
2
- import { A as ArkConfig, c as ArkConfigLoadResult } from './configTypes-VD0qcubY.js';
1
+ import { e as CreateArchitectureProfileOptions, b as ArchitectureProfile, d as ArkCheckConfig, C as CreateArchitectureProfileFromArkConfigOptions, f as CreateElevenLayerArkConfigOptions, i as Policy, j as IntentCreator, I as IntentName } from './types-fQ9-KrBj.js';
2
+ import { A as ArkConfig, c as ArkConfigLoadResult } from './configTypes-CTELo87-.js';
3
3
 
4
4
  /** Versioned public result contract types + JSON Schema (adapter envelope). */
5
5
  /**
@@ -392,7 +392,7 @@ declare function createAdapterResult(input: {
392
392
  }): CurrentAdapterResult;
393
393
 
394
394
  /** ArkGate library version — single source of truth. */
395
- declare const version = "4.8.19";
395
+ declare const version = "4.8.21";
396
396
 
397
397
  /**
398
398
  * AI Code Gate (basic).
@@ -1722,10 +1722,60 @@ type EffectiveArkRules = {
1722
1722
  /**
1723
1723
  * Pure invariant coverage evidence (ADR 0014 / AR09–AR10).
1724
1724
  *
1725
- * Mines test titles and symbol presence against the Effective ArkRules catalog.
1725
+ * Mines test titles and symbol declarations against the Effective ArkRules catalog.
1726
+ * `classifyCoverage` returns one verdict; `countsAsCoverage` is the policy;
1727
+ * `describeCoverage` is the sentence. A bare mention is never coverage.
1726
1728
  * No filesystem — Tooling supplies file contents and test globs.
1727
1729
  */
1728
1730
 
1731
+ /**
1732
+ * How a `coverage.symbol` identifier is declared. `type`, `interface`, and
1733
+ * `enum` are declarations — the same witness as function, class, const, and
1734
+ * method.
1735
+ */
1736
+ type DeclarationShape = 'function' | 'class' | 'const' | 'method' | 'type' | 'interface' | 'enum';
1737
+ /** Where a bare mention of an id or symbol sat. Never coverage. */
1738
+ type MentionContext = 'test-body' | 'comment' | 'string' | 'import';
1739
+ /**
1740
+ * One coverage verdict. Best evidence wins: test-title, then declaration,
1741
+ * then mention-only, then none. Regexes, the test-file vs source split, and
1742
+ * `Aggregate.method` lookup stay behind this value.
1743
+ */
1744
+ type CoverageEvidence = {
1745
+ kind: 'test-title';
1746
+ file: string;
1747
+ title: string;
1748
+ } | {
1749
+ kind: 'declaration';
1750
+ file: string;
1751
+ shape: DeclarationShape;
1752
+ } | {
1753
+ kind: 'mention-only';
1754
+ file: string;
1755
+ context: MentionContext;
1756
+ } | {
1757
+ kind: 'none';
1758
+ };
1759
+ /** Invariant fields the classifier reads. A full catalog entry is assignable. */
1760
+ type CoverageInvariant = {
1761
+ id: string;
1762
+ coverage?: {
1763
+ test?: boolean;
1764
+ symbol?: string;
1765
+ } | null;
1766
+ };
1767
+ /** File set the classifier reads. Tooling has already loaded the contents. */
1768
+ type CoverageFiles = {
1769
+ /** Project-relative path → file contents (tests and sources). */
1770
+ fileContents: Readonly<Record<string, string>>;
1771
+ /** Paths already classified as tests. */
1772
+ testFiles?: readonly string[];
1773
+ /**
1774
+ * Declared runner roots. A test title inside a root beats one outside it.
1775
+ * The outside-roots finding stays a separate verdict.
1776
+ */
1777
+ coverageRoots?: readonly string[];
1778
+ };
1729
1779
  type InvariantCoverageEvidence = {
1730
1780
  invariantId: string;
1731
1781
  layer: string;
@@ -1738,6 +1788,17 @@ type InvariantCoverageEvidence = {
1738
1788
  description: string;
1739
1789
  /** Test file that supplied the `test-title` evidence, when there was one. */
1740
1790
  testEvidenceFile?: string;
1791
+ /**
1792
+ * Non-test file that declares `coverage.symbol`. Absent when the name only
1793
+ * appears in an import or a call. ArkGate never runs tests; this path is the
1794
+ * declaration text, not a passing suite.
1795
+ */
1796
+ symbolEvidenceFile?: string;
1797
+ /**
1798
+ * Declaration shape when `evidence` includes `symbol`. Additive: readers that
1799
+ * only know `test-title` | `symbol` ignore it.
1800
+ */
1801
+ shape?: DeclarationShape;
1741
1802
  /**
1742
1803
  * The only covering test found sits outside `coverage.coverageRoots` — the
1743
1804
  * places the project declares its runner executes. ArkGate never runs tests,
@@ -1835,6 +1896,19 @@ type InvariantCoverageStats = {
1835
1896
  outOfRoot: number;
1836
1897
  };
1837
1898
  };
1899
+ /**
1900
+ * Best coverage evidence for `invariant` in `files`.
1901
+ * test-title beats a declaration; a declaration beats a bare mention; a bare
1902
+ * mention beats silence. Callers do not re-scan to explain the verdict.
1903
+ */
1904
+ declare function classifyCoverage(invariant: CoverageInvariant, files: CoverageFiles): CoverageEvidence;
1905
+ /**
1906
+ * Policy: a test title or any declaration counts, including type, interface,
1907
+ * and enum. A mention-only verdict never counts. `none` does not count.
1908
+ */
1909
+ declare function countsAsCoverage(ev: CoverageEvidence): boolean;
1910
+ /** Sentence for the verdict. `INVARIANT_UNCOVERED` prints this; it does not re-derive it. */
1911
+ declare function describeCoverage(invariant: CoverageInvariant, ev: CoverageEvidence): string;
1838
1912
  declare function evaluateInvariantCoverage(input: EvaluateInvariantCoverageInput): {
1839
1913
  coverage: InvariantCoverageEvidence[];
1840
1914
  violations: InvariantCoverageViolation[];
@@ -2484,4 +2558,4 @@ declare function catalogFixForRuleId(ruleId: string | null | undefined): string
2484
2558
  */
2485
2559
  declare function catalogWhyForRuleId(ruleId: string | null | undefined): string | undefined;
2486
2560
 
2487
- export { type ArchitectureChangeMapFile as $, type AdapterViolationInput as A, type AdapterResult as B, type AdapterSeverity as C, type AnalysisCapabilityUse as D, type EffectiveArkRules as E, type AnalysisCompilerOptions as F, type AnalysisCompleteness as G, type AnalysisContract as H, type AnalysisEvidence as I, type AnalysisFile as J, type AnalysisFileChange as K, type AnalysisFileInput as L, type AnalysisImportEdge as M, type AnalysisIr as N, type AnalysisMode as O, type AnalysisResult as P, type AnalysisViolation as Q, type ResolvedArkRunKernelCallKind as R, type AnalyzeArchitectureConvergenceInput as S, type AnalyzeChangeInput as T, type AnalyzePolicyDeltaInput as U, type AnalyzeProjectInput as V, type AnalyzeResolvedProjectInput as W, type ArchitectureActualChange as X, type ArchitectureChangeMap as Y, type ArchitectureChangeMapContract as Z, type ArchitectureChangeMapDependency as _, type ArkRulesFile as a, type SemanticDependencyKind as a$, type ArchitectureChangeOperation as a0, type ArchitectureConvergenceClassification as a1, type ArchitectureConvergenceFinding as a2, type ArchitectureConvergenceResult as a3, type ArchitectureDependency as a4, type ArchitectureEngineEdge as a5, type ArchitectureEngineResult as a6, type ArchitectureEngineViolation as a7, type ArkDesignDeltaResult as a8, type ArkEnforcementHost as a9, type PolicyDeltaAcknowledgement as aA, type PolicyDeltaAnalysis as aB, type PolicyDeltaClassification as aC, type PolicyDeltaFinding as aD, type PreflightResolvedChangeInput as aE, type PreparedChangeFile as aF, RESOLVED_CANDIDATE_FACTS_SCHEMA as aG, RESOLVED_CANDIDATE_FACTS_SCHEMA_VERSION as aH, type ResolvedAmbientFact as aI, type ResolvedAnalysisFile as aJ, type ResolvedAnalysisIr as aK, type ResolvedAnalysisResult as aL, type ResolvedCandidateFacts as aM, type ResolvedCandidateFactsInput as aN, type ResolvedCapability as aO, type ResolvedCapabilityFact as aP, type ResolvedChangePreflightResult as aQ, type ResolvedDependencyKind as aR, type ResolvedDependencyState as aS, type ResolvedFactsCompleteness as aT, type ResolvedFileFact as aU, type ResolvedIntentReferenceFact as aV, type ResolvedPublishFact as aW, type ResolvedSafetyFact as aX, type ResolvedSafetyKind as aY, type ResolvedSafetyReport as aZ, type SemanticDependency as a_, type ArkEnforcementState as aa, type ArkRuleSensorViolation as ab, type ChangePreflightResult as ac, type ClassShapeFact as ad, type CollectAnalysisConfigWarningsInput as ae, DIAGNOSTIC_CATALOG as af, DIAGNOSTIC_CATALOG_SCHEMA_VERSION as ag, DIAGNOSTIC_DOCS_RELATIVE_PATH as ah, DIAGNOSTIC_RULE_IDS as ai, type DesignDeltaChange as aj, type DesignDeltaEnforcementScope as ak, type DesignDeltaIdentity as al, type DesignSmellEvidence as am, type DesignSmellFinding as an, type DesignSmellId as ao, type DiagnosticCatalogEntry as ap, type DiagnosticCategory as aq, type EnforcementBoundaryState as ar, type EnforcementEvidence as as, type EnforcementEvidenceField as at, type EnforcementVerification as au, type EvaluateArchitectureGraphInput as av, type ForbiddenCapabilityUse as aw, type InvariantCoverageEvidence as ax, POLICY_DELTA_SCHEMA_VERSION as ay, type PolicyDelta as az, type ResolvedArkRunDeclarationFact as b, analyzeArchitectureConvergence as b0, analyzeChange as b1, analyzePolicyDelta as b2, analyzeProject as b3, analyzeResolvedProject as b4, buildArkRuleFileHints as b5, canPromoteInvariant as b6, catalogFixForRuleId as b7, catalogWhyForRuleId as b8, classifyArkPolicyDelta as b9, policyDeltaAcknowledgementMatches as bA, preflightChange as bB, preflightResolvedChange as bC, resolvedFactsEvidenceRequirementsHash as bD, serializeDiagnosticCatalog as bE, stableSerialize as bF, toAdapterDiagnostic as bG, version as bH, collectAnalysisConfigWarnings as ba, collectEmptyAppliesToFindings as bb, collectForbiddenCapabilityUses as bc, createAICodeGate as bd, createAdapterResult as be, createArchitectureProfile as bf, createArchitectureProfileFromArkConfig as bg, createElevenLayerArkConfig as bh, createResolvedCandidateFacts as bi, deriveArkRuleFileHints as bj, detectArchitectureCycles as bk, deterministicHash as bl, diagnosticDocsFragment as bm, diagnosticDocsPath as bn, elevenLayerProfile as bo, evaluateArchitectureGraph as bp, evaluateArkRuleSensors as bq, evaluateInvariantCoverage as br, explainViolation as bs, extractClassShapesFromSource as bt, extractSemanticDependencies as bu, getDiagnosticCatalogEntry as bv, isCataloguedOrArkRuleFamily as bw, isKnownDiagnosticCode as bx, loadContract as by, loadResolvedCandidateFacts as bz, type ResolvedArkRunKernelCallFact as c, type ResolvedArkRunManagedNewFact as d, type ResolvedDependencyFact as e, type ResolvedArkRunCompositionRootHitFact as f, type ResolvedFactsReason as g, type ResolvedArkOrderPlaneCallFact as h, type ResolvedArkOrderGenericUpdateFact as i, type ResolvedArkOrderRootHitFact as j, type ResolvedArkOrderXiFieldWriteFact as k, type ResolvedArkOrderIngestWriteFact as l, ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH as m, type AICodeGate as n, type AICodeGateContext as o, type AICodeGateOptions as p, type AICodeGateResult as q, type AICodeGateViolation as r, type AIGateExtension as s, ANALYSIS_IR_SCHEMA_VERSION as t, ARK_ANALYSIS_RESULT_SCHEMA as u, ARK_ANALYSIS_RESULT_SCHEMA_VERSION as v, ARK_DESIGN_DELTA_SCHEMA_VERSION as w, ARK_ENFORCEMENT_STATE_SCHEMA_VERSION as x, type AdapterCompletenessReason as y, type AdapterDiagnostic as z };
2561
+ export { type ArchitectureChangeMapFile as $, type AdapterViolationInput as A, type AdapterResult as B, type AdapterSeverity as C, type AnalysisCapabilityUse as D, type EffectiveArkRules as E, type AnalysisCompilerOptions as F, type AnalysisCompleteness as G, type AnalysisContract as H, type AnalysisEvidence as I, type AnalysisFile as J, type AnalysisFileChange as K, type AnalysisFileInput as L, type AnalysisImportEdge as M, type AnalysisIr as N, type AnalysisMode as O, type AnalysisResult as P, type AnalysisViolation as Q, type ResolvedArkRunKernelCallKind as R, type AnalyzeArchitectureConvergenceInput as S, type AnalyzeChangeInput as T, type AnalyzePolicyDeltaInput as U, type AnalyzeProjectInput as V, type AnalyzeResolvedProjectInput as W, type ArchitectureActualChange as X, type ArchitectureChangeMap as Y, type ArchitectureChangeMapContract as Z, type ArchitectureChangeMapDependency as _, type ArkRulesFile as a, type ResolvedPublishFact as a$, type ArchitectureChangeOperation as a0, type ArchitectureConvergenceClassification as a1, type ArchitectureConvergenceFinding as a2, type ArchitectureConvergenceResult as a3, type ArchitectureDependency as a4, type ArchitectureEngineEdge as a5, type ArchitectureEngineResult as a6, type ArchitectureEngineViolation as a7, type ArkDesignDeltaResult as a8, type ArkEnforcementHost as a9, type ForbiddenCapabilityUse as aA, type InvariantCoverageEvidence as aB, type MentionContext as aC, POLICY_DELTA_SCHEMA_VERSION as aD, type PolicyDelta as aE, type PolicyDeltaAcknowledgement as aF, type PolicyDeltaAnalysis as aG, type PolicyDeltaClassification as aH, type PolicyDeltaFinding as aI, type PreflightResolvedChangeInput as aJ, type PreparedChangeFile as aK, RESOLVED_CANDIDATE_FACTS_SCHEMA as aL, RESOLVED_CANDIDATE_FACTS_SCHEMA_VERSION as aM, type ResolvedAmbientFact as aN, type ResolvedAnalysisFile as aO, type ResolvedAnalysisIr as aP, type ResolvedAnalysisResult as aQ, type ResolvedCandidateFacts as aR, type ResolvedCandidateFactsInput as aS, type ResolvedCapability as aT, type ResolvedCapabilityFact as aU, type ResolvedChangePreflightResult as aV, type ResolvedDependencyKind as aW, type ResolvedDependencyState as aX, type ResolvedFactsCompleteness as aY, type ResolvedFileFact as aZ, type ResolvedIntentReferenceFact as a_, type ArkEnforcementState as aa, type ArkRuleSensorViolation as ab, type ChangePreflightResult as ac, type ClassShapeFact as ad, type CollectAnalysisConfigWarningsInput as ae, type CoverageEvidence as af, type CoverageFiles as ag, type CoverageInvariant as ah, DIAGNOSTIC_CATALOG as ai, DIAGNOSTIC_CATALOG_SCHEMA_VERSION as aj, DIAGNOSTIC_DOCS_RELATIVE_PATH as ak, DIAGNOSTIC_RULE_IDS as al, type DeclarationShape as am, type DesignDeltaChange as an, type DesignDeltaEnforcementScope as ao, type DesignDeltaIdentity as ap, type DesignSmellEvidence as aq, type DesignSmellFinding as ar, type DesignSmellId as as, type DiagnosticCatalogEntry as at, type DiagnosticCategory as au, type EnforcementBoundaryState as av, type EnforcementEvidence as aw, type EnforcementEvidenceField as ax, type EnforcementVerification as ay, type EvaluateArchitectureGraphInput as az, type ResolvedArkRunDeclarationFact as b, type ResolvedSafetyFact as b0, type ResolvedSafetyKind as b1, type ResolvedSafetyReport as b2, type SemanticDependency as b3, type SemanticDependencyKind as b4, analyzeArchitectureConvergence as b5, analyzeChange as b6, analyzePolicyDelta as b7, analyzeProject as b8, analyzeResolvedProject as b9, explainViolation as bA, extractClassShapesFromSource as bB, extractSemanticDependencies as bC, getDiagnosticCatalogEntry as bD, isCataloguedOrArkRuleFamily as bE, isKnownDiagnosticCode as bF, loadContract as bG, loadResolvedCandidateFacts as bH, policyDeltaAcknowledgementMatches as bI, preflightChange as bJ, preflightResolvedChange as bK, resolvedFactsEvidenceRequirementsHash as bL, serializeDiagnosticCatalog as bM, stableSerialize as bN, toAdapterDiagnostic as bO, version as bP, buildArkRuleFileHints as ba, canPromoteInvariant as bb, catalogFixForRuleId as bc, catalogWhyForRuleId as bd, classifyArkPolicyDelta as be, classifyCoverage as bf, collectAnalysisConfigWarnings as bg, collectEmptyAppliesToFindings as bh, collectForbiddenCapabilityUses as bi, countsAsCoverage as bj, createAICodeGate as bk, createAdapterResult as bl, createArchitectureProfile as bm, createArchitectureProfileFromArkConfig as bn, createElevenLayerArkConfig as bo, createResolvedCandidateFacts as bp, deriveArkRuleFileHints as bq, describeCoverage as br, detectArchitectureCycles as bs, deterministicHash as bt, diagnosticDocsFragment as bu, diagnosticDocsPath as bv, elevenLayerProfile as bw, evaluateArchitectureGraph as bx, evaluateArkRuleSensors as by, evaluateInvariantCoverage as bz, type ResolvedArkRunKernelCallFact as c, type ResolvedArkRunManagedNewFact as d, type ResolvedDependencyFact as e, type ResolvedArkRunCompositionRootHitFact as f, type ResolvedFactsReason as g, type ResolvedArkOrderPlaneCallFact as h, type ResolvedArkOrderGenericUpdateFact as i, type ResolvedArkOrderRootHitFact as j, type ResolvedArkOrderXiFieldWriteFact as k, type ResolvedArkOrderIngestWriteFact as l, ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH as m, type AICodeGate as n, type AICodeGateContext as o, type AICodeGateOptions as p, type AICodeGateResult as q, type AICodeGateViolation as r, type AIGateExtension as s, ANALYSIS_IR_SCHEMA_VERSION as t, ARK_ANALYSIS_RESULT_SCHEMA as u, ARK_ANALYSIS_RESULT_SCHEMA_VERSION as v, ARK_DESIGN_DELTA_SCHEMA_VERSION as w, ARK_ENFORCEMENT_STATE_SCHEMA_VERSION as x, type AdapterCompletenessReason as y, type AdapterDiagnostic as z };