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.
- package/CHANGELOG.md +220 -0
- package/README.md +107 -117
- package/bin/ark-check-runtime.mjs +26 -4
- package/bin/ark-layer-match.mjs +236 -12
- package/bin/ark.mjs +6 -2
- package/bin/lib/analysis-engine.mjs +13 -6
- package/bin/lib/config-contract.mjs +6 -0
- package/bin/lib/diagnostic-catalog.mjs +1 -0
- package/bin/lib/doctor-advisories.mjs +22 -1
- package/bin/lib/doctor-human.mjs +11 -9
- package/bin/lib/doctor-plan.mjs +17 -14
- package/bin/lib/enforcement-honesty.mjs +30 -2
- package/bin/lib/first-run-help.mjs +7 -1
- package/bin/lib/html-report-advisories.mjs +8 -0
- package/bin/lib/html-report-depth.mjs +12 -7
- package/bin/lib/invariant-coverage.mjs +406 -54
- package/bin/lib/managed-upgrade.mjs +59 -14
- package/bin/lib/package-manager.mjs +5 -2
- package/bin/lib/physical-cohesion.mjs +68 -51
- package/bin/lib/pilot-loop.mjs +183 -37
- package/bin/lib/reshape-decisions.mjs +2 -2
- package/bin/lib/rules-under-contract.mjs +40 -4
- package/bin/lib/start-install-recovery.mjs +156 -0
- package/bin/lib/start-preview.mjs +0 -54
- package/bin/lib/upgrade-behavior-changes.mjs +83 -0
- package/bin/lib/upgrade-command.mjs +69 -31
- package/bin/lib/upgrade-outcome.mjs +359 -0
- package/bin/lib/violations.mjs +109 -0
- package/dist/{configTypes-VD0qcubY.d.ts → configTypes-CTELo87-.d.ts} +9 -0
- package/dist/{diagnosticCatalog-D2v0ASN2.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +79 -5
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +35 -4
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +25 -18
- package/dist/index.d.ts +13 -4
- package/dist/index.js +27 -20
- package/dist/nestjs/index.cjs +9 -9
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +9 -9
- package/dist/runtime/index.cjs +18 -11
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +18 -11
- package/dist/{types-D5GT5ZT8.d.ts → types-fQ9-KrBj.d.ts} +1 -1
- package/dist/{types-BSzRy2X1.d.ts → types-yDNEQ603.d.ts} +1 -1
- package/docs/README.md +4 -3
- package/docs/agent-guide.md +15 -7
- package/docs/brownfield-adoption.md +4 -3
- package/docs/configuration.md +46 -8
- package/docs/develop.md +1 -1
- package/docs/diagnostics.md +12 -2
- package/docs/package-surface.md +4 -3
- package/docs/product-voice.md +11 -3
- package/docs/use.md +1 -1
- package/package.json +3 -3
- package/schemas/ark.config.schema.json +14 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +18 -1
- package/templates/agent-skills/ark-architect/SKILL.md +2 -0
- package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
- package/templates/agent-skills/ark-contract/SKILL.md +2 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +3 -1
- package/templates/agent-skills/ark-explain/SKILL.md +3 -1
- package/templates/agent-skills/ark-explore/SKILL.md +15 -1
- package/templates/agent-skills/ark-fix/SKILL.md +2 -0
- package/templates/agent-skills/ark-loop/SKILL.md +2 -0
- package/templates/agent-skills/ark-order/SKILL.md +2 -0
- package/templates/agent-skills/ark-place/SKILL.md +2 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
- package/templates/agent-skills/ark-think/SKILL.md +2 -0
- package/templates/agent-skills/ark-upgrade/SKILL.md +25 -10
- package/templates/skills/ark-adopt.md +18 -1
- package/templates/skills/ark-architect.md +2 -0
- package/templates/skills/ark-autopilot.md +6 -2
- package/templates/skills/ark-contract.md +2 -0
- package/templates/skills/ark-coverage.md +3 -1
- package/templates/skills/ark-explain.md +3 -1
- package/templates/skills/ark-explore.md +15 -1
- package/templates/skills/ark-fix.md +2 -0
- package/templates/skills/ark-loop.md +2 -0
- package/templates/skills/ark-order.md +2 -0
- package/templates/skills/ark-place.md +2 -0
- package/templates/skills/ark-runtime.md +2 -0
- package/templates/skills/ark-think.md +2 -0
- package/templates/skills/ark-upgrade.md +25 -10
package/bin/ark-layer-match.mjs
CHANGED
|
@@ -215,18 +215,192 @@ export function layerForRelativePath(relPath, layers) {
|
|
|
215
215
|
* Includes the parent folder so `features/auth` ≠ `modules/auth`.
|
|
216
216
|
* Identity is case-normalized for portable results across filesystems.
|
|
217
217
|
* `src/features/auth/api.ts` + folders `["features"]` → `"features/auth"`.
|
|
218
|
+
*
|
|
219
|
+
* A bare name stays that unanchored one-segment match. The child segment is
|
|
220
|
+
* never the filename, so a flat file under the parent is not its own slice.
|
|
221
|
+
* A starred prefix (`lib` / `features` / `*` / `*`) reuses the shared-root
|
|
222
|
+
* anchor: it must sit at offset 0, or at offset 1 after `src/` or `app/`.
|
|
223
|
+
* With `sliceIdentity` `"path"` (the default) the slice id includes the
|
|
224
|
+
* literal prefix plus the star bindings (`lib/features/projects/rfi`), so
|
|
225
|
+
* parallel trees get different ids. With `"stars"` it is the last literal
|
|
226
|
+
* plus those star bindings. A star never binds the filename; a file directly
|
|
227
|
+
* under the last bound directory keeps that directory as its slice.
|
|
218
228
|
*/
|
|
219
|
-
export function sliceIdForPath(relPath, sliceFolders) {
|
|
229
|
+
export function sliceIdForPath(relPath, sliceFolders, sliceIdentity) {
|
|
220
230
|
if (!sliceFolders?.length)
|
|
221
231
|
return undefined;
|
|
232
|
+
const identity = sliceIdentity === 'stars' ? 'stars' : 'path';
|
|
222
233
|
const parts = String(relPath)
|
|
223
234
|
.split(/[/\\]/)
|
|
224
235
|
.filter(Boolean);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
236
|
+
if (parts.length === 0)
|
|
237
|
+
return undefined;
|
|
238
|
+
const bare = new Set();
|
|
239
|
+
for (const raw of sliceFolders) {
|
|
240
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
241
|
+
continue;
|
|
242
|
+
if (isAnchoredSliceEntry(raw)) {
|
|
243
|
+
const anchored = anchoredSliceId(parts, raw, identity);
|
|
244
|
+
if (anchored)
|
|
245
|
+
return anchored;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (!raw.includes('/') && !raw.includes('\\') && !raw.includes('*')) {
|
|
249
|
+
bare.add(raw.toLowerCase());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return bareSliceId(parts, bare);
|
|
253
|
+
}
|
|
254
|
+
/** A starred prefix with a real leading directory, not a bare name and not `*`. */
|
|
255
|
+
function isAnchoredSliceEntry(entry) {
|
|
256
|
+
const segments = entry.split(/[/\\]/).filter((part) => part.length > 0);
|
|
257
|
+
if (segments.length < 2)
|
|
258
|
+
return false;
|
|
259
|
+
if (segments[0] === '*' || segments[0] === '**')
|
|
260
|
+
return false;
|
|
261
|
+
return segments.some((part) => part === '*');
|
|
262
|
+
}
|
|
263
|
+
function anchoredSliceId(parts, raw, identity) {
|
|
264
|
+
const pattern = raw
|
|
265
|
+
.split(/[/\\]/)
|
|
266
|
+
.filter((part) => part.length > 0)
|
|
267
|
+
.map((part) => part.toLowerCase());
|
|
268
|
+
const offsets = anchorOffsets(parts, pattern[0] ?? '');
|
|
269
|
+
for (const offset of offsets) {
|
|
270
|
+
const id = bindAnchoredSlice(parts, pattern, offset, identity);
|
|
271
|
+
if (id)
|
|
272
|
+
return id;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Walk an anchored pattern from `offset`. Literals must match. Each matching
|
|
278
|
+
* literal is part of the bound segments, and each `*` binds one directory.
|
|
279
|
+
* A star stops before the filename; leftover stars then end the id at the
|
|
280
|
+
* last directory that did bind. Segments `lib` / `features` / `*` / `*`
|
|
281
|
+
* (the pattern `lib/features` plus two stars) therefore yield
|
|
282
|
+
* `lib/features/projects/rfi` under `path`.
|
|
283
|
+
*
|
|
284
|
+
* `path` returns every bound segment (literal prefix plus star bindings), so
|
|
285
|
+
* a parallel tree (`components` / `features` / `*` / `*`) gets a different
|
|
286
|
+
* id. `stars` drops the literal prefix before the last literal and keeps
|
|
287
|
+
* that literal plus the star bindings.
|
|
288
|
+
*/
|
|
289
|
+
function bindAnchoredSlice(parts, pattern, offset, identity = 'path') {
|
|
290
|
+
const bound = [];
|
|
291
|
+
let lastLiteralAt = -1;
|
|
292
|
+
let index = offset;
|
|
293
|
+
for (let pi = 0; pi < pattern.length; pi += 1) {
|
|
294
|
+
const segment = pattern[pi];
|
|
295
|
+
if (segment === '*') {
|
|
296
|
+
if (index >= parts.length - 1) {
|
|
297
|
+
for (let rest = pi; rest < pattern.length; rest += 1) {
|
|
298
|
+
if (pattern[rest] !== '*')
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
bound.push(parts[index].toLowerCase());
|
|
304
|
+
index += 1;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (segment === '**' || index >= parts.length)
|
|
308
|
+
return undefined;
|
|
309
|
+
if (parts[index].toLowerCase() !== segment)
|
|
310
|
+
return undefined;
|
|
311
|
+
bound.push(parts[index].toLowerCase());
|
|
312
|
+
lastLiteralAt = bound.length - 1;
|
|
313
|
+
index += 1;
|
|
314
|
+
}
|
|
315
|
+
if (bound.length === 0)
|
|
316
|
+
return undefined;
|
|
317
|
+
if (identity === 'stars') {
|
|
318
|
+
if (lastLiteralAt < 0)
|
|
319
|
+
return undefined;
|
|
320
|
+
return bound.slice(lastLiteralAt).join('/');
|
|
321
|
+
}
|
|
322
|
+
return bound.join('/');
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Doctor warning input. Silent unless `sliceIdentity` is `"stars"` and two
|
|
326
|
+
* different anchored prefixes share a stem (last literal plus the stars after it).
|
|
327
|
+
*/
|
|
328
|
+
export function sliceIdentityCollisions(rules) {
|
|
329
|
+
const out = [];
|
|
330
|
+
for (const rule of rules ?? []) {
|
|
331
|
+
if (!rule || rule.sliceIdentity !== 'stars')
|
|
332
|
+
continue;
|
|
333
|
+
const groups = new Map();
|
|
334
|
+
const seen = new Map();
|
|
335
|
+
for (const raw of rule.sliceFolders ?? []) {
|
|
336
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
337
|
+
continue;
|
|
338
|
+
const stem = starsIdentityStem(raw);
|
|
339
|
+
if (!stem)
|
|
340
|
+
continue;
|
|
341
|
+
const key = raw.split(/[/\\]/).filter((part) => part.length > 0).join('/').toLowerCase();
|
|
342
|
+
const groupSeen = seen.get(stem) ?? new Set();
|
|
343
|
+
if (groupSeen.has(key))
|
|
344
|
+
continue;
|
|
345
|
+
groupSeen.add(key);
|
|
346
|
+
seen.set(stem, groupSeen);
|
|
347
|
+
const paths = groups.get(stem) ?? [];
|
|
348
|
+
paths.push(raw);
|
|
349
|
+
groups.set(stem, paths);
|
|
229
350
|
}
|
|
351
|
+
for (const [stem, paths] of groups) {
|
|
352
|
+
if (paths.length < 2)
|
|
353
|
+
continue;
|
|
354
|
+
out.push({
|
|
355
|
+
from: rule.from,
|
|
356
|
+
to: rule.to,
|
|
357
|
+
stem,
|
|
358
|
+
paths,
|
|
359
|
+
message: formatSliceIdentityCollision(rule.from, rule.to, paths, stem),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
/** Last literal plus the stars that follow it. `**` never forms a stem. */
|
|
366
|
+
function starsIdentityStem(raw) {
|
|
367
|
+
if (!isAnchoredSliceEntry(raw))
|
|
368
|
+
return undefined;
|
|
369
|
+
const segments = raw
|
|
370
|
+
.split(/[/\\]/)
|
|
371
|
+
.filter((part) => part.length > 0)
|
|
372
|
+
.map((part) => part.toLowerCase());
|
|
373
|
+
let lastLiteral = -1;
|
|
374
|
+
for (let i = 0; i < segments.length; i += 1) {
|
|
375
|
+
const part = segments[i] ?? '';
|
|
376
|
+
if (part === '**')
|
|
377
|
+
return undefined;
|
|
378
|
+
if (part !== '*')
|
|
379
|
+
lastLiteral = i;
|
|
380
|
+
}
|
|
381
|
+
if (lastLiteral < 0)
|
|
382
|
+
return undefined;
|
|
383
|
+
const tail = segments.slice(lastLiteral);
|
|
384
|
+
if (tail.some((part, index) => index > 0 && part !== '*'))
|
|
385
|
+
return undefined;
|
|
386
|
+
return tail.join('/');
|
|
387
|
+
}
|
|
388
|
+
function formatSliceIdentityCollision(from, to, paths, stem) {
|
|
389
|
+
const named = paths.length === 2
|
|
390
|
+
? `${paths[0]} and ${paths[1]}`
|
|
391
|
+
: `${paths.slice(0, -1).join(', ')}, and ${paths[paths.length - 1]}`;
|
|
392
|
+
return `${from} → ${to}: sliceIdentity "stars" gives one slice id to ${named}. They bind as ${stem}.`;
|
|
393
|
+
}
|
|
394
|
+
/** Bare names: leftmost folder, plus the next directory. Never the filename. */
|
|
395
|
+
function bareSliceId(parts, names) {
|
|
396
|
+
if (names.size === 0)
|
|
397
|
+
return undefined;
|
|
398
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
399
|
+
if (!names.has(parts[i].toLowerCase()))
|
|
400
|
+
continue;
|
|
401
|
+
if (i + 1 >= parts.length - 1)
|
|
402
|
+
continue;
|
|
403
|
+
return `${parts[i].toLowerCase()}/${parts[i + 1].toLowerCase()}`;
|
|
230
404
|
}
|
|
231
405
|
return undefined;
|
|
232
406
|
}
|
|
@@ -279,6 +453,16 @@ function trimTrailingSlashes(value) {
|
|
|
279
453
|
}
|
|
280
454
|
/** Source folders a declared shared root may sit under without being named. */
|
|
281
455
|
const SHARED_ROOT_SOURCE_PREFIXES = ['src', 'app'];
|
|
456
|
+
/**
|
|
457
|
+
* Anchor at segment 0, or also at segment 1 when the path opens with `src/` or
|
|
458
|
+
* `app/` and the declaration does not itself start with that folder.
|
|
459
|
+
* Shared roots and starred slice prefixes share this offset.
|
|
460
|
+
*/
|
|
461
|
+
function anchorOffsets(parts, rootHead) {
|
|
462
|
+
const head = (parts[0] ?? '').toLowerCase();
|
|
463
|
+
const root = rootHead.toLowerCase();
|
|
464
|
+
return SHARED_ROOT_SOURCE_PREFIXES.includes(head) && root !== head ? [0, 1] : [0];
|
|
465
|
+
}
|
|
282
466
|
/** A root that would disable the wall wholesale is not a root. */
|
|
283
467
|
function isBlanketRoot(raw) {
|
|
284
468
|
const trimmed = trimTrailingSlashes(raw.replace(/^[./]+/, ''));
|
|
@@ -287,7 +471,7 @@ function isBlanketRoot(raw) {
|
|
|
287
471
|
/**
|
|
288
472
|
* Is `relPath` under one of the roots the rule declares shared on purpose?
|
|
289
473
|
*
|
|
290
|
-
* **Anchored
|
|
474
|
+
* **Anchored**: the root must start the repo-relative
|
|
291
475
|
* path, optionally after a single conventional source folder, so `ui` covers
|
|
292
476
|
* `ui/button.tsx` and `src/ui/button.tsx` but NOT `modules/a/ui/x.tsx` — an
|
|
293
477
|
* unanchored root would exempt a whole tree the author never declared. Deeper
|
|
@@ -317,9 +501,7 @@ export function pathUnderSharedRoot(relPath, sharedRoots) {
|
|
|
317
501
|
const root = normalizeSegments(raw);
|
|
318
502
|
if (root.length === 0)
|
|
319
503
|
continue;
|
|
320
|
-
|
|
321
|
-
// folder the root does not itself name.
|
|
322
|
-
const offsets = SHARED_ROOT_SOURCE_PREFIXES.includes(parts[0]) && root[0] !== parts[0] ? [0, 1] : [0];
|
|
504
|
+
const offsets = anchorOffsets(parts, root[0] ?? '');
|
|
323
505
|
for (const offset of offsets) {
|
|
324
506
|
if (offset + root.length > parts.length)
|
|
325
507
|
continue;
|
|
@@ -364,7 +546,8 @@ export function crossSliceEdgeAllowed(allowedCrossSlice, fromSlice, toSlice) {
|
|
|
364
546
|
* cross-slice edge, is the repo telling us its design, so it is no longer
|
|
365
547
|
* "unclassifiable". Order: no fromPath → no slice folders → intent (no toPath):
|
|
366
548
|
* shared-root allow / slice fail-closed → a side that is neither in a slice nor
|
|
367
|
-
* declared shared →
|
|
549
|
+
* declared shared → shared root importing a slice (only when the rule denies
|
|
550
|
+
* that hop) → same slice → declared cross edge → deny.
|
|
368
551
|
*
|
|
369
552
|
* Intent/event names are not files. Callers pass fromPath only and must not
|
|
370
553
|
* invent a toPath (that would mis-slice). A declared shared root is classified
|
|
@@ -386,6 +569,14 @@ export function peerIsolationDecision(input) {
|
|
|
386
569
|
const toClassified = Boolean(input.toSlice) || input.toShared === true;
|
|
387
570
|
if (!fromClassified || !toClassified)
|
|
388
571
|
return { denied: true, reason: 'unclassifiable-path' };
|
|
572
|
+
// Shared roots are a sink. The hop is allowed unless the rule opts in.
|
|
573
|
+
// allowedCrossSlice does not excuse it: this is not a slice-to-slice edge.
|
|
574
|
+
if (input.sharedImportsSlice === 'deny' &&
|
|
575
|
+
input.fromShared === true &&
|
|
576
|
+
Boolean(input.toSlice) &&
|
|
577
|
+
!input.fromSlice) {
|
|
578
|
+
return { denied: true, reason: 'shared-imports-slice' };
|
|
579
|
+
}
|
|
389
580
|
// At least one side is declared shared (and carries no slice id): the repo
|
|
390
581
|
// said this code belongs to no slice, so there is no cross-slice edge here.
|
|
391
582
|
if (!input.fromSlice || !input.toSlice)
|
|
@@ -416,6 +607,8 @@ export function peerIsolationDenyExplanation(reason, context) {
|
|
|
416
607
|
switch (reason) {
|
|
417
608
|
case 'cross-slice':
|
|
418
609
|
return `cross-slice edge ${context.fromSlice ?? '?'} → ${context.toSlice ?? '?'}. Extract the shared code, use events/ports across slices, or declare the edge in the rule's allowedCrossSlice.`;
|
|
610
|
+
case 'shared-imports-slice':
|
|
611
|
+
return `shared root imports slice ${context.toSlice ?? '?'} (${context.fromPath ?? '?'} → ${context.toPath ?? '?'}). The wall is direct-only.`;
|
|
419
612
|
case 'unclassifiable-path': {
|
|
420
613
|
const unplaced = [
|
|
421
614
|
context.fromSlice ? undefined : context.fromPath,
|
|
@@ -463,8 +656,10 @@ export function findDeniedEdgeDecision(rules, from, to, options) {
|
|
|
463
656
|
const fromPath = options?.fromPath;
|
|
464
657
|
const toPath = options?.toPath;
|
|
465
658
|
const folders = resolveSliceFolders(rule, from, options?.layers);
|
|
466
|
-
const fromSlice = fromPath
|
|
467
|
-
|
|
659
|
+
const fromSlice = fromPath
|
|
660
|
+
? sliceIdForPath(fromPath, folders, rule.sliceIdentity)
|
|
661
|
+
: undefined;
|
|
662
|
+
const toSlice = toPath ? sliceIdForPath(toPath, folders, rule.sliceIdentity) : undefined;
|
|
468
663
|
const decision = peerIsolationDecision({
|
|
469
664
|
fromPath,
|
|
470
665
|
toPath,
|
|
@@ -474,6 +669,7 @@ export function findDeniedEdgeDecision(rules, from, to, options) {
|
|
|
474
669
|
fromShared: !fromSlice && pathUnderSharedRoot(fromPath, rule.sharedRoots),
|
|
475
670
|
toShared: !toSlice && pathUnderSharedRoot(toPath, rule.sharedRoots),
|
|
476
671
|
crossSliceAllowed: crossSliceEdgeAllowed(rule.allowedCrossSlice, fromSlice, toSlice),
|
|
672
|
+
sharedImportsSlice: rule.sharedImportsSlice,
|
|
477
673
|
});
|
|
478
674
|
if (decision.denied) {
|
|
479
675
|
return { rule, peerIsolationReason: decision.reason, fromSlice, toSlice };
|
|
@@ -487,6 +683,34 @@ export function findDeniedEdgeDecision(rules, from, to, options) {
|
|
|
487
683
|
}
|
|
488
684
|
return undefined;
|
|
489
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Shared-root → slice edges the check still allows. Listed so a green run
|
|
688
|
+
* says the wall is direct-only. When the rule sets `sharedImportsSlice: "deny"`
|
|
689
|
+
* the same hop is a violation instead, so it is not listed twice.
|
|
690
|
+
*/
|
|
691
|
+
export function findSharedImportsSliceBridge(rules, from, to, options) {
|
|
692
|
+
const fromPath = options?.fromPath;
|
|
693
|
+
const toPath = options?.toPath;
|
|
694
|
+
if (!fromPath || !toPath)
|
|
695
|
+
return undefined;
|
|
696
|
+
for (const rule of rules ?? []) {
|
|
697
|
+
if (rule.from !== from || rule.to !== to)
|
|
698
|
+
continue;
|
|
699
|
+
if (rule.allowed !== false || !rule.peerIsolation)
|
|
700
|
+
continue;
|
|
701
|
+
if (rule.sharedImportsSlice === 'deny')
|
|
702
|
+
continue;
|
|
703
|
+
const folders = resolveSliceFolders(rule, from, options?.layers);
|
|
704
|
+
const fromSlice = sliceIdForPath(fromPath, folders, rule.sliceIdentity);
|
|
705
|
+
const toSlice = sliceIdForPath(toPath, folders, rule.sliceIdentity);
|
|
706
|
+
if (fromSlice || !toSlice)
|
|
707
|
+
continue;
|
|
708
|
+
if (!pathUnderSharedRoot(fromPath, rule.sharedRoots))
|
|
709
|
+
continue;
|
|
710
|
+
return { fromPath, toPath, toSlice };
|
|
711
|
+
}
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
490
714
|
export function isEdgeDenied(rules, from, to, options) {
|
|
491
715
|
return findDeniedEdgeRule(rules, from, to, options) !== undefined;
|
|
492
716
|
}
|
package/bin/ark.mjs
CHANGED
|
@@ -26,11 +26,13 @@ import {
|
|
|
26
26
|
applyStartPreview,
|
|
27
27
|
emitStartSetupBudgetRefuse,
|
|
28
28
|
evaluateStartSetupBudgetGate,
|
|
29
|
-
formatStartPackageInstallFailure,
|
|
30
29
|
planStart,
|
|
31
30
|
renderStartPreview,
|
|
32
|
-
runStartPackageInstall,
|
|
33
31
|
} from './lib/start-preview.mjs';
|
|
32
|
+
import {
|
|
33
|
+
formatStartPackageInstallFailure,
|
|
34
|
+
runStartPackageInstall,
|
|
35
|
+
} from './lib/start-install-recovery.mjs';
|
|
34
36
|
import { runUpgradeCommand } from './lib/upgrade-command.mjs';
|
|
35
37
|
import { detectActiveAgentHost } from './lib/skill-install.mjs';
|
|
36
38
|
import { loadArkConfigContract } from './lib/config-contract.mjs';
|
|
@@ -509,6 +511,7 @@ async function start(args) {
|
|
|
509
511
|
exitStatus: install.status,
|
|
510
512
|
installCommand,
|
|
511
513
|
hostOutput: `${install.stdout}\n${install.stderr}`,
|
|
514
|
+
packageVersion: cliVersion(),
|
|
512
515
|
})
|
|
513
516
|
);
|
|
514
517
|
return install.status;
|
|
@@ -584,6 +587,7 @@ async function start(args) {
|
|
|
584
587
|
exitStatus: installStatus,
|
|
585
588
|
installCommand: `${command} ${commandArgs.join(' ')}`,
|
|
586
589
|
hostOutput,
|
|
590
|
+
packageVersion: cliVersion(),
|
|
587
591
|
})
|
|
588
592
|
);
|
|
589
593
|
}
|