arkgate 3.5.0 → 3.6.0
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 +37 -1
- package/bin/lib/contract-smells.mjs +52 -13
- package/bin/lib/doctor-advisories.mjs +16 -3
- package/bin/lib/doctor-plan.mjs +5 -5
- package/bin/lib/html-report-advisories.mjs +54 -8
- package/bin/lib/physical-cohesion.mjs +230 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/agent-guide.md +11 -1
- package/docs/package-surface.md +2 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/skills/ark-architect.md +9 -0
- package/templates/skills/ark-fix.md +7 -0
- package/templates/skills/ark-loop.md +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,9 +4,45 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 3.6.0 — 2026-07-17
|
|
8
|
+
|
|
9
|
+
Phase X closes: the doctor learns to see **physical shape** and agents get a governed way to
|
|
10
|
+
execute reorganizations, plus three field warm-ups from the 3.5.0 the field adopter validation.
|
|
11
|
+
Everything remains **advisory** — no verdict, exit-code, `designFitness`, or `patternBets`
|
|
12
|
+
change. **No breaking** CLI or `ark.config.json` changes. **No gate weaken. No apply path.**
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Physical cohesion sensor (X04, ADR 0010):** `doctor.physicalCohesion` reports domain
|
|
17
|
+
concepts exploded across mirrored directory clusters — concentration, not volume (dispersed
|
|
18
|
+
`use-*` hooks never fire). Deterministic path/name tokenization (framework filenames take the
|
|
19
|
+
topmost meaningful path segment; monorepo scaffold roots are never a concept); fixed
|
|
20
|
+
corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20); findings ranked and
|
|
21
|
+
capped honestly; anchors under `app/`/`pages/` are `fixedByConvention`. `notAScore` — facts,
|
|
22
|
+
never a score or gate input.
|
|
23
|
+
- **Reshape pilot (X04):** `physicalCohesion.reshapePilot.nextPilot` is a **proposed, never
|
|
24
|
+
applied** card — one at a time, smallest convention-free anchor, `moveSample`/`movesTotal`,
|
|
25
|
+
`successSignal`, `killSwitch`, hard `doNot[]`. Real moves run only through the write gate and
|
|
26
|
+
atomic preflight via `/ark-loop`; merges are `/ark-architect` **merge cards** (domain
|
|
27
|
+
modeling, **never a codemod**); `/ark-fix` never folds reshapes into a fix batch. The
|
|
28
|
+
consolidation target subtree is never re-proposed as a source — the loop converges (validated
|
|
29
|
+
end to end: pilot → gate → kill switch → judgment → convergence).
|
|
30
|
+
- **Stale acknowledgments (X05):** ack entries matching no detected edge (orphaned, unknown id,
|
|
31
|
+
typo) land in `contractHealth.ackLifecycle` as `staleCount` + `stale[]` (sorted, capped);
|
|
32
|
+
doctor and report name the exact entries to fix or delete, even at zero visible smells.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **Mid-name families (X06):** the family-infra carve-out matches the target's family token
|
|
37
|
+
against ANY source token (`HoursPersistenceAdapters -> PersistenceInfrastructure` goes
|
|
38
|
+
quiet); generic role words (`adapter(s)`/`gateway(s)`) never count as a family, so
|
|
39
|
+
`AdaptersCore` is not every `*Adapters` layer's base.
|
|
40
|
+
- **Report evidence overflow (X07):** per-finding evidence lists announce their 6-item cap with
|
|
41
|
+
an honest `(+N more)` marker; expired/stale lifecycle notes carry the same honesty.
|
|
42
|
+
|
|
7
43
|
## 3.5.0 — 2026-07-16
|
|
8
44
|
|
|
9
|
-
Field-feedback release (Phase X, from
|
|
45
|
+
Field-feedback release (Phase X, from an internal field-adoption session): the HTML report reaches
|
|
10
46
|
parity with the doctor and stays there by an executable rule, contract-smell acknowledgments gain
|
|
11
47
|
a lifecycle so migration acks cannot fossilize, and the lateral-adapter smell stops firing on a
|
|
12
48
|
family's own infrastructure base. Everything remains **advisory** — no verdict, `designFitness`,
|
|
@@ -63,19 +63,26 @@ function nameTokens(name) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* X03 — an adapter reaching its OWN family's infrastructure base is not a
|
|
67
|
-
* lateral peer:
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
66
|
+
* X03/X06 — an adapter reaching its OWN family's infrastructure base is not a
|
|
67
|
+
* lateral peer: the target reads as `<Family><InfraWords…>` and the source
|
|
68
|
+
* carries the family token ANYWHERE in its name (X06, field corpus names
|
|
69
|
+
* domain-scoped adapters `HoursPersistenceAdapters` over
|
|
70
|
+
* `PersistenceInfrastructure` — the family sits mid-name). EVERY remaining
|
|
71
|
+
* target token must be an infra word (Infra/Base/Core/Shared/…) —
|
|
72
|
+
* `PaymentsCoreAdapters` is still a sibling, not a base. The reverse
|
|
73
|
+
* direction (base → member) never matches: the target must BE the base.
|
|
71
74
|
* Name heuristic like the role regexes above — a miss costs a warning line.
|
|
72
75
|
*/
|
|
73
76
|
function isFamilyInfrastructureEdge(from, to) {
|
|
74
77
|
const fromTokens = nameTokens(from);
|
|
75
78
|
const toTokens = nameTokens(to);
|
|
76
79
|
if (fromTokens.length === 0 || toTokens.length < 2) return false;
|
|
77
|
-
const family = toTokens[0];
|
|
78
|
-
|
|
80
|
+
const family = toTokens[0].toLowerCase();
|
|
81
|
+
// A generic role word is not a family: `AdaptersCore` must not read as the
|
|
82
|
+
// "Adapters family" base for every *Adapters layer — that would silently
|
|
83
|
+
// quiet genuine cross-family edges. (`Persistence` stays a valid family.)
|
|
84
|
+
if (/^(adapters?|gateways?)$/.test(family)) return false;
|
|
85
|
+
if (family.length < 2 || !fromTokens.some((t) => t.toLowerCase() === family)) return false;
|
|
79
86
|
return toTokens.slice(1).every((t) => FAMILY_INFRA_RE.test(t));
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -290,9 +297,28 @@ export function analyzeContractSmells(
|
|
|
290
297
|
}
|
|
291
298
|
}
|
|
292
299
|
|
|
300
|
+
// X05 — an ack that matches no detected edge is stale: orphaned by a fixed
|
|
301
|
+
// contract, a quieted heuristic, or a typo. Detected BEFORE ack filtering.
|
|
302
|
+
const detectedEdges = new Map();
|
|
303
|
+
for (const [id, entries] of Object.entries(findings)) {
|
|
304
|
+
detectedEdges.set(id, new Set(entries.map((e) => e.edge).filter((e) => e != null)));
|
|
305
|
+
}
|
|
306
|
+
const staleEdges = [];
|
|
307
|
+
if (ackState && !ackState.invalid && Array.isArray(ackState.acks)) {
|
|
308
|
+
for (const a of ackState.acks) {
|
|
309
|
+
const canonical = normalizeAckEdge(a.id, a.edge);
|
|
310
|
+
if (canonical != null && detectedEdges.get(a.id)?.has(canonical)) continue;
|
|
311
|
+
staleEdges.push({ id: a.id, edge: a.edge });
|
|
312
|
+
}
|
|
313
|
+
// Stable under sidecar reordering, like every other output here.
|
|
314
|
+
staleEdges.sort((a, b) =>
|
|
315
|
+
a.id === b.id ? (a.edge < b.edge ? -1 : a.edge > b.edge ? 1 : 0) : a.id < b.id ? -1 : 1
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
293
319
|
const smells = [];
|
|
294
320
|
let matchedAcks = 0;
|
|
295
|
-
const ackLifecycle = { undated: 0, malformed: 0, expired: [] };
|
|
321
|
+
const ackLifecycle = { undated: 0, malformed: 0, expired: [], stale: staleEdges };
|
|
296
322
|
for (const id of CONTRACT_SMELL_IDS) {
|
|
297
323
|
const entries = findings[id];
|
|
298
324
|
if (!entries || entries.length === 0) continue;
|
|
@@ -545,9 +571,9 @@ export function formatContractHealthLines(smells, health) {
|
|
|
545
571
|
const gw = health?.governanceWeight;
|
|
546
572
|
const weightNoteworthy = gw?.weight === 'heavy' || gw?.weight === 'light';
|
|
547
573
|
const lc = health?.ackLifecycle;
|
|
548
|
-
// Undated acks must surface even when every smell is suppressed —
|
|
549
|
-
//
|
|
550
|
-
const lifecycleNoteworthy = (lc?.undated ?? 0) > 0;
|
|
574
|
+
// Undated and stale acks must surface even when every smell is suppressed —
|
|
575
|
+
// fossilization (X02) and orphaned entries (X05) hide exactly there.
|
|
576
|
+
const lifecycleNoteworthy = (lc?.undated ?? 0) > 0 || (lc?.staleCount ?? 0) > 0;
|
|
551
577
|
if (list.length === 0 && !health?.ackFile?.invalid && !weightNoteworthy && !lifecycleNoteworthy) {
|
|
552
578
|
return rows;
|
|
553
579
|
}
|
|
@@ -591,6 +617,14 @@ export function formatContractHealthLines(smells, health) {
|
|
|
591
617
|
text: `${lc.undated} applied acknowledgment(s) have no review-by date — add one so migration acks cannot fossilize.`,
|
|
592
618
|
});
|
|
593
619
|
}
|
|
620
|
+
if ((lc?.staleCount ?? 0) > 0) {
|
|
621
|
+
const shown = (lc.stale ?? []).slice(0, 4).map((s) => s.edge);
|
|
622
|
+
const more = lc.staleCount > shown.length ? ` …(+${lc.staleCount - shown.length} more)` : '';
|
|
623
|
+
rows.push({
|
|
624
|
+
mark: 'dim',
|
|
625
|
+
text: `${lc.staleCount} acknowledgment(s) match no detected edge — stale; fix the edge string or delete the entry: ${shown.join(', ')}${more}`,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
594
628
|
if (weightNoteworthy) {
|
|
595
629
|
rows.push({
|
|
596
630
|
mark: 'warn',
|
|
@@ -610,20 +644,23 @@ export function formatContractHealthLines(smells, health) {
|
|
|
610
644
|
* `acknowledged` counts ack entries that MATCHED a detected edge (stale acks count 0).
|
|
611
645
|
* X02 — `ackLifecycle` reports how applied acks age: `undated` applied without
|
|
612
646
|
* a review-by, `expired` past it (no longer applied), `malformed` bad dates.
|
|
647
|
+
* X05 — `stale` counts ack entries matching NO detected edge (orphaned or
|
|
648
|
+
* typo'd); they suppress nothing and should be fixed or deleted.
|
|
613
649
|
*
|
|
614
650
|
* @param {ReturnType<typeof detectContractSmells>} smells
|
|
615
651
|
* @param {ReturnType<typeof loadContractSmellAcks>} ackState
|
|
616
652
|
* @param {number} [matchedAcks]
|
|
617
|
-
* @param {{ undated: number, malformed: number, expired: Array<{id: string, edge: string, reviewBy: string}> }} [ackLifecycle]
|
|
653
|
+
* @param {{ undated: number, malformed: number, expired: Array<{id: string, edge: string, reviewBy: string}>, stale: Array<{id: string, edge: string}> }} [ackLifecycle]
|
|
618
654
|
*/
|
|
619
655
|
export function summarizeContractHealth(
|
|
620
656
|
smells,
|
|
621
657
|
ackState = { exists: false, acks: [] },
|
|
622
658
|
matchedAcks = 0,
|
|
623
|
-
ackLifecycle = { undated: 0, malformed: 0, expired: [] }
|
|
659
|
+
ackLifecycle = { undated: 0, malformed: 0, expired: [], stale: [] }
|
|
624
660
|
) {
|
|
625
661
|
const list = Array.isArray(smells) ? smells : [];
|
|
626
662
|
const expired = Array.isArray(ackLifecycle?.expired) ? ackLifecycle.expired : [];
|
|
663
|
+
const stale = Array.isArray(ackLifecycle?.stale) ? ackLifecycle.stale : [];
|
|
627
664
|
return {
|
|
628
665
|
status: list.length > 0 ? 'contract-smells' : 'ok',
|
|
629
666
|
smellCount: list.length,
|
|
@@ -634,6 +671,8 @@ export function summarizeContractHealth(
|
|
|
634
671
|
malformed: ackLifecycle?.malformed ?? 0,
|
|
635
672
|
expiredCount: expired.length,
|
|
636
673
|
expired: expired.slice(0, MAX_EVIDENCE),
|
|
674
|
+
staleCount: stale.length,
|
|
675
|
+
stale: stale.slice(0, MAX_EVIDENCE),
|
|
637
676
|
},
|
|
638
677
|
advisory: true,
|
|
639
678
|
label:
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Doctor's advisory sensors, aggregated (W01 contract health + U05 ambient
|
|
3
|
-
* state). Advisory only: nothing here feeds a
|
|
4
|
-
* exit code. One seam keeps doctor-plan.mjs
|
|
5
|
-
* advisory surfaces land.
|
|
3
|
+
* state + X04 physical cohesion). Advisory only: nothing here feeds a
|
|
4
|
+
* verdict, designFitness, or an exit code. One seam keeps doctor-plan.mjs
|
|
5
|
+
* inside its module budget as new advisory surfaces land.
|
|
6
6
|
*/
|
|
7
7
|
import { computeAmbientState, printAmbientStateSection } from './ambient-state.mjs';
|
|
8
8
|
import { computeContractHealth, printContractHealthSection } from './contract-smells.mjs';
|
|
9
|
+
import {
|
|
10
|
+
computePhysicalCohesion,
|
|
11
|
+
computeReshapePilot,
|
|
12
|
+
printPhysicalCohesionSection,
|
|
13
|
+
} from './physical-cohesion.mjs';
|
|
9
14
|
|
|
10
15
|
export function computeDoctorAdvisories(root, config, cov, rules, files, ts) {
|
|
16
|
+
const physicalCohesion = computePhysicalCohesion(root, files);
|
|
17
|
+
physicalCohesion.reshapePilot = computeReshapePilot(physicalCohesion, files, root);
|
|
11
18
|
return {
|
|
12
19
|
contractHealth: computeContractHealth(root, config, cov, rules),
|
|
13
20
|
ambientState: computeAmbientState(ts, root, config, files),
|
|
21
|
+
physicalCohesion,
|
|
14
22
|
};
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
export function printDoctorAdvisories(advisories, io) {
|
|
18
26
|
printContractHealthSection(advisories.contractHealth, io);
|
|
19
27
|
printAmbientStateSection(advisories.ambientState, io);
|
|
28
|
+
printPhysicalCohesionSection(
|
|
29
|
+
advisories.physicalCohesion,
|
|
30
|
+
advisories.physicalCohesion?.reshapePilot,
|
|
31
|
+
io
|
|
32
|
+
);
|
|
20
33
|
}
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -55,7 +55,6 @@ function normalize(value) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
|
|
59
58
|
export function computeCoverage(root, config, files, rules) {
|
|
60
59
|
const layers = config.layers ?? [];
|
|
61
60
|
const counts = new Map(layers.map((layer) => [layer.name, 0]));
|
|
@@ -425,7 +424,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
425
424
|
patternBets: patternBetsForLoop,
|
|
426
425
|
designSmells,
|
|
427
426
|
});
|
|
428
|
-
const { contractHealth, ambientState } = computeDoctorAdvisories(root, config, cov, rules, files, options.ts); // W01+U05 advisories — never a verdict
|
|
427
|
+
const { contractHealth, ambientState, physicalCohesion } = computeDoctorAdvisories(root, config, cov, rules, files, options.ts); // W01+U05+X04 advisories — never a verdict
|
|
429
428
|
|
|
430
429
|
if (asJson) {
|
|
431
430
|
console.log(
|
|
@@ -462,10 +461,11 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
462
461
|
goldenPattern,
|
|
463
462
|
// Q04: one-pilot loop (extraction card → re-doctor).
|
|
464
463
|
pilotLoop,
|
|
465
|
-
//
|
|
464
|
+
// Advisories, never a verdict: W01 contract health, U05 ambient
|
|
465
|
+
// state (opt-in), X04 physical cohesion + proposed reshape pilot.
|
|
466
466
|
contractHealth,
|
|
467
|
-
// U05: ambient-state sensor (advisory; opt-in; verdict unchanged).
|
|
468
467
|
ambientState,
|
|
468
|
+
physicalCohesion,
|
|
469
469
|
governed: cov.governed,
|
|
470
470
|
emptyLayers: cov.emptyLayers,
|
|
471
471
|
layersWithoutRules: cov.layersWithoutRules,
|
|
@@ -647,7 +647,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
647
647
|
);
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
printDoctorAdvisories({ contractHealth, ambientState }, { line, warn, color }); // advisory sections
|
|
650
|
+
printDoctorAdvisories({ contractHealth, ambientState, physicalCohesion }, { line, warn, color }); // advisory sections
|
|
651
651
|
|
|
652
652
|
console.log('');
|
|
653
653
|
console.log(color.bold('Coverage'));
|
|
@@ -52,11 +52,13 @@ function ackLifecycleHtml(lc) {
|
|
|
52
52
|
if (!lc) return '';
|
|
53
53
|
const rows = [];
|
|
54
54
|
if ((lc.expiredCount ?? 0) > 0) {
|
|
55
|
-
const
|
|
55
|
+
const list = lc.expired ?? [];
|
|
56
|
+
const edges = list
|
|
56
57
|
.map((e) => `<code>${esc(e.edge)}</code> (review-by ${esc(e.reviewBy)})`)
|
|
57
58
|
.join(' · ');
|
|
59
|
+
const more = lc.expiredCount > list.length ? ` …(+${lc.expiredCount - list.length} more)` : '';
|
|
58
60
|
rows.push(
|
|
59
|
-
`<p><span class="tag warn">expired</span> ${lc.expiredCount} acknowledgment(s) past review-by — no longer applied, the smell is active again: ${edges}</p>`
|
|
61
|
+
`<p><span class="tag warn">expired</span> ${lc.expiredCount} acknowledgment(s) past review-by — no longer applied, the smell is active again: ${edges}${more}</p>`
|
|
60
62
|
);
|
|
61
63
|
}
|
|
62
64
|
if ((lc.malformed ?? 0) > 0) {
|
|
@@ -69,6 +71,15 @@ function ackLifecycleHtml(lc) {
|
|
|
69
71
|
`<p class="muted">${lc.undated} applied acknowledgment(s) have no review-by date — add one so migration acks cannot fossilize.</p>`
|
|
70
72
|
);
|
|
71
73
|
}
|
|
74
|
+
if ((lc.staleCount ?? 0) > 0) {
|
|
75
|
+
// Plain "+N more": doctor JSON caps its own list, so pointing there for
|
|
76
|
+
// the remainder would over-promise (cross-model review finding).
|
|
77
|
+
const edges = (lc.stale ?? []).slice(0, 4).map((s) => `<code>${esc(s.edge)}</code>`).join(' · ');
|
|
78
|
+
const more = lc.staleCount > 4 ? ` …(+${lc.staleCount - 4} more)` : '';
|
|
79
|
+
rows.push(
|
|
80
|
+
`<p class="muted">${lc.staleCount} acknowledgment(s) match no detected edge — stale; fix the edge string or delete the entry: ${edges}${more}</p>`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
72
83
|
return rows.join('\n');
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -85,15 +96,21 @@ function contractHealthHtml(health) {
|
|
|
85
96
|
const body = smells.length === 0
|
|
86
97
|
? `<p class="muted">No contract smells detected — no explicitly bidirectional allows, peripheral-into-core allows, lateral adapter allows, or dead rules beyond what is acknowledged.</p>`
|
|
87
98
|
: smells
|
|
88
|
-
.map(
|
|
89
|
-
(s)
|
|
99
|
+
.map((s) => {
|
|
100
|
+
const evidence = Array.isArray(s.evidence) ? s.evidence : [];
|
|
101
|
+
// X07 — the cap must announce itself: a 12-edge smell showing 6
|
|
102
|
+
// codes with no marker reads as the whole story.
|
|
103
|
+
const more = evidence.length > 6
|
|
104
|
+
? ` <span class="muted">…(+${evidence.length - 6} more in doctor JSON)</span>`
|
|
105
|
+
: '';
|
|
106
|
+
return `
|
|
90
107
|
<div class="finding">
|
|
91
108
|
<p><span class="tag warn">${esc(s.id)}</span> ${esc(s.outcome ?? s.message ?? '')}</p>
|
|
92
109
|
<p class="muted">${esc(s.message ?? '')}</p>
|
|
93
|
-
<p class="muted">evidence: <code>${
|
|
110
|
+
<p class="muted">evidence: <code>${evidence.slice(0, 6).map(esc).join('</code> · <code>')}</code>${more}</p>
|
|
94
111
|
<p class="muted">fix: ${esc(s.fix ?? '')}</p>
|
|
95
|
-
</div
|
|
96
|
-
)
|
|
112
|
+
</div>`;
|
|
113
|
+
})
|
|
97
114
|
.join('\n');
|
|
98
115
|
return `
|
|
99
116
|
<section data-advisory="contractHealth">
|
|
@@ -135,6 +152,31 @@ function ambientStateHtml(state) {
|
|
|
135
152
|
</section>`;
|
|
136
153
|
}
|
|
137
154
|
|
|
155
|
+
function physicalCohesionHtml(pc) {
|
|
156
|
+
if (!pc) return '';
|
|
157
|
+
const findings = Array.isArray(pc.findings) ? pc.findings : [];
|
|
158
|
+
const body = findings.length === 0
|
|
159
|
+
? '<p class="muted">No mirrored concept explosion detected — no concept clusters over the calibrated thresholds (ADR 0010).</p>'
|
|
160
|
+
: findings
|
|
161
|
+
.map((f) => {
|
|
162
|
+
const anchors = (f.anchors ?? [])
|
|
163
|
+
.map((a) => `<code>${esc(a.path)}</code> (${a.files}${a.fixedByConvention ? ', fixed by convention' : ''})`)
|
|
164
|
+
.join(' · ');
|
|
165
|
+
return `<p><span class="tag warn">${esc(f.concept)}</span> ${f.files} file(s) across ${f.anchorCount} anchor(s)${f.mirrored ? ' — mirrored' : ''}: ${anchors}</p>`;
|
|
166
|
+
})
|
|
167
|
+
.join('\n') +
|
|
168
|
+
(pc.truncated > 0 ? `<p class="muted">…(+${pc.truncated} more concept(s) in doctor JSON)</p>` : '');
|
|
169
|
+
const pilot = pc.reshapePilot?.nextPilot
|
|
170
|
+
? `<p class="muted">next pilot (proposed, never applied): ${esc(pc.reshapePilot.nextPilot.pilotTarget)} — one pilot at a time via /ark-loop; merges are judgment cards only.</p>`
|
|
171
|
+
: '';
|
|
172
|
+
return `
|
|
173
|
+
<section data-advisory="physicalCohesion">
|
|
174
|
+
<h2>Physical cohesion <span class="muted">(advisory — facts, not a score; the verdict is unchanged)</span></h2>
|
|
175
|
+
${body}
|
|
176
|
+
${pilot}
|
|
177
|
+
</section>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
138
180
|
/**
|
|
139
181
|
* Render every doctor advisory as report sections. Keys must cover everything
|
|
140
182
|
* `computeDoctorAdvisories` returns — the parity guard enforces it.
|
|
@@ -143,7 +185,11 @@ function ambientStateHtml(state) {
|
|
|
143
185
|
export function renderAdvisorySections(advisories, escape) {
|
|
144
186
|
if (!advisories || typeof advisories !== 'object') return '';
|
|
145
187
|
if (typeof escape === 'function') esc = escape;
|
|
146
|
-
return [
|
|
188
|
+
return [
|
|
189
|
+
contractHealthHtml(advisories.contractHealth),
|
|
190
|
+
ambientStateHtml(advisories.ambientState),
|
|
191
|
+
physicalCohesionHtml(advisories.physicalCohesion),
|
|
192
|
+
]
|
|
147
193
|
.filter(Boolean)
|
|
148
194
|
.join('\n');
|
|
149
195
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X04 (R1/R2) — physicalCohesion: advisory sensor for mirrored concept
|
|
3
|
+
* explosion, plus the proposed (never applied) reshape pilot card.
|
|
4
|
+
*
|
|
5
|
+
* ArkGate proves edges; this sensor sees SHAPE: one domain concept exploded
|
|
6
|
+
* into large file clusters across parallel directory families (field origin:
|
|
7
|
+
* the field corpus `projects` concept = 221 route files + 146 handlers + 124 repositories,
|
|
8
|
+
* all invisible to every edge-based surface). Facts only — `notAScore`,
|
|
9
|
+
* never a verdict/designFitness/patternBets input, never a gate.
|
|
10
|
+
*
|
|
11
|
+
* The signal is CONCENTRATION, not volume: React `use-*` hooks are hundreds
|
|
12
|
+
* of files across hundreds of directories and healthy. Thresholds are fixed
|
|
13
|
+
* constants calibrated on the field corpus (ADR 0010 D3), not tunables.
|
|
14
|
+
* Concept extraction is name/path heuristic (ADR 0010 D2) — same discipline
|
|
15
|
+
* as W01 layer roles: a miss costs a warning line, never a verdict.
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
|
|
19
|
+
/** ADR 0010 D3 — corpus-calibrated, fixed. */
|
|
20
|
+
const CLUSTER_MIN = 40;
|
|
21
|
+
const MIRROR_MIN = 20;
|
|
22
|
+
const MIRROR_ANCHORS = 2;
|
|
23
|
+
const MAX_FINDINGS = 5;
|
|
24
|
+
const MAX_ANCHORS = 4;
|
|
25
|
+
const MAX_MOVE_SAMPLE = 5;
|
|
26
|
+
|
|
27
|
+
const FRAMEWORK_FILES = /^(route|page|layout|index|loading|error|template|default|not-found|middleware|actions?|handler)$/i;
|
|
28
|
+
const SKIP_SEGMENT = /^(\[.*\]|\(.*\)|src|app|apps|api|lib|libs|pages|packages|modules|components|utils|helpers|hooks|server|client|shared|common|__tests__|tests?|e2e|examples?|dist|build)$/i;
|
|
29
|
+
const NOISE_TOKEN = /^(use|api|get|set|app|lib|the|new)$/;
|
|
30
|
+
/** ADR 0010 D7 — framework-owned anchors never move. */
|
|
31
|
+
const CONVENTION_ANCHOR_RE = /(^|\/)(app|pages)(\/|$)/;
|
|
32
|
+
const EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
33
|
+
|
|
34
|
+
function nameTokens(name) {
|
|
35
|
+
return (String(name).match(/[A-Z]?[a-z0-9]+|[A-Z]+(?![a-z])/g) ?? []).map((t) => t.toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function firstMeaningful(tokens) {
|
|
39
|
+
for (const t of tokens) {
|
|
40
|
+
if (t.length >= 3 && !NOISE_TOKEN.test(t)) return t;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deterministic concept + anchor for one repo-relative file (ADR 0010 D2).
|
|
47
|
+
* Non-framework files: first meaningful basename token, anchored at their
|
|
48
|
+
* directory. Framework files (route.ts, page.tsx, …): the TOPMOST meaningful
|
|
49
|
+
* path segment is the concept; the anchor is the path above it — the subtree
|
|
50
|
+
* that mirrors. Returns null when nothing meaningful is found.
|
|
51
|
+
*/
|
|
52
|
+
export function classifyPhysical(rel) {
|
|
53
|
+
const norm = String(rel).split(path.sep).join('/');
|
|
54
|
+
if (!EXT_RE.test(norm)) return null;
|
|
55
|
+
const segs = norm.split('/');
|
|
56
|
+
const base = segs.at(-1).replace(EXT_RE, '');
|
|
57
|
+
if (!FRAMEWORK_FILES.test(base)) {
|
|
58
|
+
const concept = firstMeaningful(nameTokens(base));
|
|
59
|
+
return concept ? { concept, anchor: segs.slice(0, -1).join('/') || '.' } : null;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
62
|
+
if (SKIP_SEGMENT.test(segs[i])) continue;
|
|
63
|
+
const concept = firstMeaningful(nameTokens(segs[i]));
|
|
64
|
+
if (concept) return { concept, anchor: segs.slice(0, i).join('/') || '.' };
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compute the physicalCohesion advisory over the governed file list.
|
|
71
|
+
* @param {string} root
|
|
72
|
+
* @param {string[]} files absolute governed file paths
|
|
73
|
+
*/
|
|
74
|
+
export function computePhysicalCohesion(root, files) {
|
|
75
|
+
const clusters = new Map(); // concept -> Map(anchor -> count)
|
|
76
|
+
let analyzed = 0;
|
|
77
|
+
for (const abs of Array.isArray(files) ? files : []) {
|
|
78
|
+
const rel = path.relative(root, abs);
|
|
79
|
+
if (rel.startsWith('..')) continue;
|
|
80
|
+
const r = classifyPhysical(rel);
|
|
81
|
+
if (!r) continue;
|
|
82
|
+
analyzed += 1;
|
|
83
|
+
if (!clusters.has(r.concept)) clusters.set(r.concept, new Map());
|
|
84
|
+
const m = clusters.get(r.concept);
|
|
85
|
+
m.set(r.anchor, (m.get(r.anchor) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const findings = [];
|
|
89
|
+
for (const [concept, m] of clusters) {
|
|
90
|
+
const anchors = [...m.entries()]
|
|
91
|
+
.map(([anchor, count]) => ({
|
|
92
|
+
path: anchor,
|
|
93
|
+
files: count,
|
|
94
|
+
fixedByConvention: CONVENTION_ANCHOR_RE.test(`${anchor}/`),
|
|
95
|
+
}))
|
|
96
|
+
.sort((a, b) => b.files - a.files || (a.path < b.path ? -1 : 1));
|
|
97
|
+
const maxCluster = anchors[0].files;
|
|
98
|
+
const bigAnchors = anchors.filter((a) => a.files >= MIRROR_MIN);
|
|
99
|
+
const mirrored = bigAnchors.length >= MIRROR_ANCHORS;
|
|
100
|
+
if (maxCluster < CLUSTER_MIN && !mirrored) continue;
|
|
101
|
+
const total = anchors.reduce((n, a) => n + a.files, 0);
|
|
102
|
+
findings.push({
|
|
103
|
+
concept,
|
|
104
|
+
files: total,
|
|
105
|
+
maxCluster,
|
|
106
|
+
mirrored,
|
|
107
|
+
anchors: anchors.filter((a) => a.files >= MIRROR_MIN).slice(0, MAX_ANCHORS),
|
|
108
|
+
anchorCount: anchors.length,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
findings.sort((a, b) => b.maxCluster - a.maxCluster || (a.concept < b.concept ? -1 : 1));
|
|
112
|
+
const kept = findings.slice(0, MAX_FINDINGS);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
advisory: true,
|
|
116
|
+
notAScore: true,
|
|
117
|
+
analyzedFiles: analyzed,
|
|
118
|
+
findingCount: findings.length,
|
|
119
|
+
truncated: Math.max(0, findings.length - kept.length),
|
|
120
|
+
findings: kept,
|
|
121
|
+
label:
|
|
122
|
+
findings.length > 0
|
|
123
|
+
? `Physical cohesion: ${findings.length} concept(s) exploded across large mirrored clusters — advisory; the gate verdict is unchanged`
|
|
124
|
+
: 'Physical cohesion: no mirrored concept explosion detected',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* R2 — the proposed reshape pilot for the TOP finding (one at a time, Q04
|
|
130
|
+
* discipline; ADR 0010 D4–D7). Proposal only: no apply path exists. Moves
|
|
131
|
+
* target the smallest convention-free anchor; sampled `to` paths must fall
|
|
132
|
+
* under the consolidated feature directory the agent will create via the
|
|
133
|
+
* governed write path (T02 preflight validates every real move there).
|
|
134
|
+
*/
|
|
135
|
+
export function computeReshapePilot(cohesion, files, root) {
|
|
136
|
+
const top = cohesion?.findings?.[0];
|
|
137
|
+
if (!top) return null;
|
|
138
|
+
// Recompute the FULL anchor map for the top concept: the finding's anchors
|
|
139
|
+
// are display-filtered (>= MIRROR_MIN, capped), and pilot selection over
|
|
140
|
+
// that trimmed list falsely reported "nothing to move" when the only
|
|
141
|
+
// movable anchor sat below the display floor (cross-model review finding).
|
|
142
|
+
const byAnchor = new Map();
|
|
143
|
+
const relOf = (abs) => path.relative(root, abs).split(path.sep).join('/');
|
|
144
|
+
for (const abs of Array.isArray(files) ? files : []) {
|
|
145
|
+
const rel = relOf(abs);
|
|
146
|
+
const r = classifyPhysical(rel);
|
|
147
|
+
if (!r || r.concept !== top.concept) continue;
|
|
148
|
+
byAnchor.set(r.anchor, (byAnchor.get(r.anchor) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
const targetDir = `src/features/${top.concept}`;
|
|
151
|
+
const movable = [...byAnchor.entries()]
|
|
152
|
+
.filter(
|
|
153
|
+
([anchor]) =>
|
|
154
|
+
!CONVENTION_ANCHOR_RE.test(`${anchor}/`) &&
|
|
155
|
+
// The consolidation target subtree is DONE, not a source — without
|
|
156
|
+
// this the loop re-proposes the files it just moved, forever
|
|
157
|
+
// (end-to-end pilot-loop finding).
|
|
158
|
+
anchor !== targetDir &&
|
|
159
|
+
!anchor.startsWith(`${targetDir}/`)
|
|
160
|
+
)
|
|
161
|
+
.map(([anchor, count]) => ({ path: anchor, files: count }))
|
|
162
|
+
.sort((a, b) => a.files - b.files || (a.path < b.path ? -1 : 1));
|
|
163
|
+
if (movable.length === 0) {
|
|
164
|
+
return {
|
|
165
|
+
proposed: true,
|
|
166
|
+
applied: false,
|
|
167
|
+
neverMechanicalSafe: true,
|
|
168
|
+
concept: top.concept,
|
|
169
|
+
note: 'Every remaining anchor for this concept is fixed by framework convention or already consolidated — nothing to move; consider the merge-card review instead.',
|
|
170
|
+
nextPilot: null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Smallest movable cluster worth piloting; if none reaches the floor, take
|
|
174
|
+
// the largest movable anchor so the pilot still exists and stays honest.
|
|
175
|
+
const pilotAnchor = movable.find((a) => a.files >= 10) ?? movable[movable.length - 1];
|
|
176
|
+
const rels = (Array.isArray(files) ? files : [])
|
|
177
|
+
.map(relOf)
|
|
178
|
+
.filter((rel) => {
|
|
179
|
+
const r = classifyPhysical(rel);
|
|
180
|
+
return r && r.concept === top.concept && r.anchor === pilotAnchor.path;
|
|
181
|
+
})
|
|
182
|
+
.sort();
|
|
183
|
+
const moves = rels.slice(0, MAX_MOVE_SAMPLE).map((rel) => ({
|
|
184
|
+
from: rel,
|
|
185
|
+
to: `${targetDir}/${rel.split('/').at(-1)}`,
|
|
186
|
+
}));
|
|
187
|
+
return {
|
|
188
|
+
proposed: true,
|
|
189
|
+
applied: false,
|
|
190
|
+
neverMechanicalSafe: true,
|
|
191
|
+
concept: top.concept,
|
|
192
|
+
nextPilot: {
|
|
193
|
+
pilotTarget: `${top.concept} @ ${pilotAnchor.path} (${pilotAnchor.files} file(s))`,
|
|
194
|
+
move: `Consolidate the ${top.concept} cluster from ${pilotAnchor.path} under ${targetDir}/ — one anchor only, moves proposed as an architecture change map and validated by the atomic preflight before any write.`,
|
|
195
|
+
moveSample: moves,
|
|
196
|
+
movesTotal: rels.length,
|
|
197
|
+
successSignal: `re-run doctor: the ${top.concept} cluster count drops and the verdict stays green`,
|
|
198
|
+
killSwitch: 'revert this move set; nothing else was touched',
|
|
199
|
+
doNot: [
|
|
200
|
+
'never move files under app/ or pages/ — fixed by framework convention',
|
|
201
|
+
'one pilot at a time; re-doctor before the next card exists',
|
|
202
|
+
'merges are judgment cards only — never mechanical, never a codemod',
|
|
203
|
+
'never weaken the contract to make a reshape pass',
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Doctor human section (advisory). Silent when there is nothing to say.
|
|
211
|
+
* @param {{ line: (mark: string, text: string) => void, warn: string, color: { bold: (s: string) => string, dim: (s: string) => string } }} io
|
|
212
|
+
*/
|
|
213
|
+
export function printPhysicalCohesionSection(cohesion, pilot, io) {
|
|
214
|
+
if (!cohesion || cohesion.findingCount === 0) return;
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log(io.color.bold('Physical cohesion (advisory)'));
|
|
217
|
+
for (const f of cohesion.findings) {
|
|
218
|
+
const anchors = f.anchors
|
|
219
|
+
.map((a) => `${a.path} (${a.files}${a.fixedByConvention ? ', fixed by convention' : ''})`)
|
|
220
|
+
.join(' · ');
|
|
221
|
+
io.line(io.warn, `[${f.concept}] ${f.files} file(s) in ${f.anchorCount} anchor(s): ${anchors}`);
|
|
222
|
+
}
|
|
223
|
+
if (cohesion.truncated > 0) {
|
|
224
|
+
io.line(' ', io.color.dim(`…(+${cohesion.truncated} more concept(s) in doctor JSON)`));
|
|
225
|
+
}
|
|
226
|
+
if (pilot?.nextPilot) {
|
|
227
|
+
io.line(' ', io.color.dim(`next pilot: ${pilot.nextPilot.pilotTarget} — proposed only, run it via /ark-loop`));
|
|
228
|
+
}
|
|
229
|
+
io.line(' ', io.color.dim('advisory only — facts, not a score; the gate verdict and design fitness are unchanged'));
|
|
230
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var Se=Object.defineProperty;var yt=Object.getOwnPropertyDescriptor;var gt=Object.getOwnPropertyNames;var mt=Object.prototype.hasOwnProperty;var ht=(e,t)=>{for(var n in t)Se(e,n,{get:t[n],enumerable:!0})},At=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of gt(t))!mt.call(e,i)&&i!==n&&Se(e,i,{get:()=>t[i],enumerable:!(r=yt(t,i))||r.enumerable});return e};var bt=e=>At(Se({},"__esModule",{value:!0}),e);var an={};ht(an,{ANALYSIS_IR_SCHEMA_VERSION:()=>ge,ARK_ANALYSIS_RESULT_SCHEMA:()=>Ke,ARK_ANALYSIS_RESULT_SCHEMA_VERSION:()=>Ye,ARK_CONFIG_SCHEMA:()=>fe,ARK_CONFIG_SCHEMA_VERSION:()=>at,POLICY_DELTA_SCHEMA_VERSION:()=>Ae,analyzeArchitectureConvergence:()=>ee,analyzeChange:()=>re,analyzePolicyDelta:()=>Ve,analyzeProject:()=>U,classifyArkPolicyDelta:()=>be,collectAnalysisConfigWarnings:()=>Ue,collectForbiddenCapabilityUses:()=>N,createAICodeGate:()=>Te,createAdapterResult:()=>qe,createArchitectureProfile:()=>Q,createArchitectureProfileFromArkConfig:()=>Fe,createElevenLayerArkConfig:()=>je,detectArchitectureCycles:()=>Ie,deterministicHash:()=>P,elevenLayerProfile:()=>Z,evaluateArchitectureGraph:()=>ie,explainViolation:()=>Ge,extractSemanticDependencies:()=>_,loadArkConfigContract:()=>J,loadContract:()=>ne,parseArkConfigJson:()=>ye,policyDeltaAcknowledgementMatches:()=>Ce,preflightChange:()=>He,stableSerialize:()=>L,toAdapterDiagnostic:()=>oe,version:()=>Be});module.exports=bt(an);var Be="3.5.0";var Ye="1.1";function k(e){return typeof e=="string"&&e.length>0?e:void 0}function ze(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Ct(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${k(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function oe(e,t="error"){let n=k(e.ruleId)??k(e.code)??"ARK_UNKNOWN",r=e.severity==="warning"?"warning":t,i={...k(e.target)?{target:k(e.target)}:{},...k(e.fromLayer)?{fromLayer:k(e.fromLayer)}:{},...k(e.toLayer)?{toLayer:k(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:r,message:k(e.message)??n,location:{file:k(e.file)??"<unknown>",line:ze(e.line,1),column:ze(e.column,1)},evidence:i,nextAction:k(e.nextAction)??Ct(n,i,e)}}function qe(e){return{schemaVersion:"1.1",valid:e.valid,diagnostics:[...(e.violations??[]).map(t=>oe(t,"error")),...(e.warnings??[]).map(t=>oe(t,"warning"))]}}var Ke={$schema:"https://json-schema.org/draft/2020-12/schema",$id:"https://unpkg.com/arkgate@2/schemas/ark.analysis-result.schema.json",title:"ArkGate analysis result",type:"object",additionalProperties:!1,required:["schemaVersion","valid","diagnostics"],properties:{schemaVersion:{const:"1.1"},valid:{type:"boolean"},diagnostics:{type:"array",items:{type:"object",additionalProperties:!1,required:["ruleId","severity","message","location","evidence"],properties:{ruleId:{type:"string",minLength:1},severity:{enum:["error","warning"]},message:{type:"string",minLength:1},location:{type:"object",additionalProperties:!1,required:["file","line","column"],properties:{file:{type:"string",minLength:1},line:{type:"integer",minimum:1},column:{type:"integer",minimum:1}}},evidence:{type:"object",additionalProperties:!1,properties:{target:{type:"string"},fromLayer:{type:"string"},toLayer:{type:"string"},typeOnly:{type:"boolean"}}},nextAction:{type:"string",minLength:1}}}}}};var Ee=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),we=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),Le=Object.freeze(Object.keys(we).sort()),ke=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function B(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=ke[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=ke[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:ke[e.slice(0,a)]??null}function se(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=we[r];if(i)return i}return null}function ce(e){if(e?.pure===!0)return[...Ee].sort();let n=(e?.capabilities?.deny??[]).filter(r=>Ee.includes(r));return[...new Set(n)].sort()}function Pe(e,t){if(t.length===0)return!1;let n=new Set(t),r=e.split(".");for(let i=r.length;i>=1;i-=1)if(n.has(r.slice(0,i).join(".")))return!0;return!1}function le(e){let t=new Set,n=new Set,r=Object.keys(we);for(let i of e?.forbiddenGlobals??[]){let a=r.filter(s=>s===i||s.startsWith(`${i}.`));if(a.length===0)n.add(i);else for(let s of a)t.add(`ambient:${s}`)}for(let i of ce(e)){t.add(`import:${i}`);for(let a of r)se(a)===i&&t.add(`ambient:${a}`)}return{atoms:[...t].sort(),rawGlobals:[...n].sort()}}var We=new Map;function Je(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function Oe(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function It(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function z(e){let t=We.get(e);if(t)return t;let n=Oe(e),r=It(n),i="",a=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(i+=Je(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(i+="(?:.*/)?",c+=2):(i+=".*",c+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=Je(p)}let s=new RegExp(`^${i}$`);return We.set(e,s),s}function Re(e){let t=Oe(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function Y(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(s=>z(s).test(n))){for(let s of a.patterns??[])if(z(s).test(n)){let c=Re(s);c>i&&(i=c,r=a.name)}}return r}function Ze(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function xt(e){let t=new Set;for(let n of e??[]){let i=Oe(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let s=i[a];if((s==="**"||s==="*")&&a>0){let c=i[a-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function St(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return xt(r?.patterns)}function v(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,s=r?.toPath;if(!a||!s)continue;let c=St(i,t,r?.layers);if(c.length===0)continue;let p=Ze(a,c),y=Ze(s,c);if(!p||!y)continue;if(p!==y)return i;continue}if(t!==n)return i}}function j(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function et(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Qe(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function tt(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function $e(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ve(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):$e(t,r)}catch{a=void 0}return!!a?.declarations?.some(s=>s.getSourceFile().fileName===n.fileName)}function _(e,t){let n,r=[],i=(s,c,p,y=!1)=>r.push({specifier:p,kind:c,line:et(t,s),typeOnly:y,unresolved:p===void 0,node:s}),a=s=>{if(e.isImportDeclaration(s))i(s,"import",j(e,s.moduleSpecifier),Qe(e,s));else if(e.isExportDeclaration(s)&&s.moduleSpecifier)i(s,"export",j(e,s.moduleSpecifier),Qe(e,s));else if(e.isImportEqualsDeclaration(s)&&e.isExternalModuleReference(s.moduleReference))i(s,"require",j(e,s.moduleReference.expression));else if(e.isCallExpression(s)){let c=s.expression.kind===e.SyntaxKind.ImportKeyword,y=e.isIdentifier(s.expression)&&s.expression.text==="require"&&!ve(e,n??(n=tt(e,t)),t,s.expression);(c||y)&&i(s,y?"require":"dynamic-import",j(e,s.arguments[0]))}e.forEachChild(s,a)};return a(t),r}function kt(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=j(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function Et(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function Xe(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function N(e,t,n){if(n.length===0)return[];let r=new Set(n),i=tt(e,t),a=new Map,s=new Set;for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations)e.isIdentifier(o.name)&&s.add(o.name.text);let c=l=>{let o=kt(e,l);if(!o)return;let g=$e(i,o.root),f=g?a.get(g):void 0;return f?[...f,...o.segments.slice(1)]:ve(e,i,t,o.root)||s.has(o.root.text)?void 0:o.segments};for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations){if(!o.initializer||!e.isIdentifier(o.name))continue;let g=c(o.initializer),f=$e(i,o.name);!g||!f||a.set(f,g)}let p=[],y=new Set,u=(l,o)=>{let g=et(t,o),f=`${l}:${o.getStart(t)}`;y.has(f)||(y.add(f),p.push({name:l,line:g,node:o}))},m=l=>{let o=l.parent&&(e.isPropertyAccessExpression(l.parent)||e.isElementAccessExpression(l.parent))&&l.parent.expression===l;if((e.isPropertyAccessExpression(l)||e.isElementAccessExpression(l))&&!o){let g=c(l),f=g?Xe(r,g):void 0;f&&u(f,l)}else e.isIdentifier(l)&&r.has(l.text)&&Et(e,l)&&!ve(e,i,t,l)&&u(l.text,l);if(e.isVariableDeclaration(l)&&e.isObjectBindingPattern(l.name)&&l.initializer){let g=c(l.initializer);if(g)for(let f of l.name.elements){if(!e.isIdentifier(f.name))continue;let I=f.propertyName?j(e,f.propertyName)??f.propertyName.text:f.name.text,x=Xe(r,[...g,I]);x&&u(x,l.initializer)}}e.forEachChild(l,m)};return m(t),p}function _e(e,t){let n=[];for(let r of _(e,t)){if(r.typeOnly||!r.specifier)continue;let i=B(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of N(e,t,Le)){let i=se(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var Ne={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function M(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Me(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&M(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:Ne.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:Ne.PUBLISH_MISSING_SOURCE}),t}function b(e,t,n){return{ruleId:e,code:e,message:t,...n}}function V(e,t){return e.slice(0,t).split(`
|
|
1
|
+
"use strict";var Se=Object.defineProperty;var yt=Object.getOwnPropertyDescriptor;var gt=Object.getOwnPropertyNames;var mt=Object.prototype.hasOwnProperty;var ht=(e,t)=>{for(var n in t)Se(e,n,{get:t[n],enumerable:!0})},At=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of gt(t))!mt.call(e,i)&&i!==n&&Se(e,i,{get:()=>t[i],enumerable:!(r=yt(t,i))||r.enumerable});return e};var bt=e=>At(Se({},"__esModule",{value:!0}),e);var an={};ht(an,{ANALYSIS_IR_SCHEMA_VERSION:()=>ge,ARK_ANALYSIS_RESULT_SCHEMA:()=>Ke,ARK_ANALYSIS_RESULT_SCHEMA_VERSION:()=>Ye,ARK_CONFIG_SCHEMA:()=>fe,ARK_CONFIG_SCHEMA_VERSION:()=>at,POLICY_DELTA_SCHEMA_VERSION:()=>Ae,analyzeArchitectureConvergence:()=>ee,analyzeChange:()=>re,analyzePolicyDelta:()=>Ve,analyzeProject:()=>U,classifyArkPolicyDelta:()=>be,collectAnalysisConfigWarnings:()=>Ue,collectForbiddenCapabilityUses:()=>N,createAICodeGate:()=>Te,createAdapterResult:()=>qe,createArchitectureProfile:()=>Q,createArchitectureProfileFromArkConfig:()=>Fe,createElevenLayerArkConfig:()=>je,detectArchitectureCycles:()=>Ie,deterministicHash:()=>P,elevenLayerProfile:()=>Z,evaluateArchitectureGraph:()=>ie,explainViolation:()=>Ge,extractSemanticDependencies:()=>_,loadArkConfigContract:()=>J,loadContract:()=>ne,parseArkConfigJson:()=>ye,policyDeltaAcknowledgementMatches:()=>Ce,preflightChange:()=>He,stableSerialize:()=>L,toAdapterDiagnostic:()=>oe,version:()=>Be});module.exports=bt(an);var Be="3.6.0";var Ye="1.1";function k(e){return typeof e=="string"&&e.length>0?e:void 0}function ze(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Ct(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${k(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function oe(e,t="error"){let n=k(e.ruleId)??k(e.code)??"ARK_UNKNOWN",r=e.severity==="warning"?"warning":t,i={...k(e.target)?{target:k(e.target)}:{},...k(e.fromLayer)?{fromLayer:k(e.fromLayer)}:{},...k(e.toLayer)?{toLayer:k(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:r,message:k(e.message)??n,location:{file:k(e.file)??"<unknown>",line:ze(e.line,1),column:ze(e.column,1)},evidence:i,nextAction:k(e.nextAction)??Ct(n,i,e)}}function qe(e){return{schemaVersion:"1.1",valid:e.valid,diagnostics:[...(e.violations??[]).map(t=>oe(t,"error")),...(e.warnings??[]).map(t=>oe(t,"warning"))]}}var Ke={$schema:"https://json-schema.org/draft/2020-12/schema",$id:"https://unpkg.com/arkgate@2/schemas/ark.analysis-result.schema.json",title:"ArkGate analysis result",type:"object",additionalProperties:!1,required:["schemaVersion","valid","diagnostics"],properties:{schemaVersion:{const:"1.1"},valid:{type:"boolean"},diagnostics:{type:"array",items:{type:"object",additionalProperties:!1,required:["ruleId","severity","message","location","evidence"],properties:{ruleId:{type:"string",minLength:1},severity:{enum:["error","warning"]},message:{type:"string",minLength:1},location:{type:"object",additionalProperties:!1,required:["file","line","column"],properties:{file:{type:"string",minLength:1},line:{type:"integer",minimum:1},column:{type:"integer",minimum:1}}},evidence:{type:"object",additionalProperties:!1,properties:{target:{type:"string"},fromLayer:{type:"string"},toLayer:{type:"string"},typeOnly:{type:"boolean"}}},nextAction:{type:"string",minLength:1}}}}}};var Ee=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),we=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),Le=Object.freeze(Object.keys(we).sort()),ke=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function B(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=ke[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=ke[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:ke[e.slice(0,a)]??null}function se(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=we[r];if(i)return i}return null}function ce(e){if(e?.pure===!0)return[...Ee].sort();let n=(e?.capabilities?.deny??[]).filter(r=>Ee.includes(r));return[...new Set(n)].sort()}function Pe(e,t){if(t.length===0)return!1;let n=new Set(t),r=e.split(".");for(let i=r.length;i>=1;i-=1)if(n.has(r.slice(0,i).join(".")))return!0;return!1}function le(e){let t=new Set,n=new Set,r=Object.keys(we);for(let i of e?.forbiddenGlobals??[]){let a=r.filter(s=>s===i||s.startsWith(`${i}.`));if(a.length===0)n.add(i);else for(let s of a)t.add(`ambient:${s}`)}for(let i of ce(e)){t.add(`import:${i}`);for(let a of r)se(a)===i&&t.add(`ambient:${a}`)}return{atoms:[...t].sort(),rawGlobals:[...n].sort()}}var We=new Map;function Je(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function Oe(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function It(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function z(e){let t=We.get(e);if(t)return t;let n=Oe(e),r=It(n),i="",a=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(i+=Je(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(i+="(?:.*/)?",c+=2):(i+=".*",c+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=Je(p)}let s=new RegExp(`^${i}$`);return We.set(e,s),s}function Re(e){let t=Oe(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function Y(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(s=>z(s).test(n))){for(let s of a.patterns??[])if(z(s).test(n)){let c=Re(s);c>i&&(i=c,r=a.name)}}return r}function Ze(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function xt(e){let t=new Set;for(let n of e??[]){let i=Oe(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let s=i[a];if((s==="**"||s==="*")&&a>0){let c=i[a-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function St(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return xt(r?.patterns)}function v(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,s=r?.toPath;if(!a||!s)continue;let c=St(i,t,r?.layers);if(c.length===0)continue;let p=Ze(a,c),y=Ze(s,c);if(!p||!y)continue;if(p!==y)return i;continue}if(t!==n)return i}}function j(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function et(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Qe(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function tt(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function $e(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ve(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):$e(t,r)}catch{a=void 0}return!!a?.declarations?.some(s=>s.getSourceFile().fileName===n.fileName)}function _(e,t){let n,r=[],i=(s,c,p,y=!1)=>r.push({specifier:p,kind:c,line:et(t,s),typeOnly:y,unresolved:p===void 0,node:s}),a=s=>{if(e.isImportDeclaration(s))i(s,"import",j(e,s.moduleSpecifier),Qe(e,s));else if(e.isExportDeclaration(s)&&s.moduleSpecifier)i(s,"export",j(e,s.moduleSpecifier),Qe(e,s));else if(e.isImportEqualsDeclaration(s)&&e.isExternalModuleReference(s.moduleReference))i(s,"require",j(e,s.moduleReference.expression));else if(e.isCallExpression(s)){let c=s.expression.kind===e.SyntaxKind.ImportKeyword,y=e.isIdentifier(s.expression)&&s.expression.text==="require"&&!ve(e,n??(n=tt(e,t)),t,s.expression);(c||y)&&i(s,y?"require":"dynamic-import",j(e,s.arguments[0]))}e.forEachChild(s,a)};return a(t),r}function kt(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=j(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function Et(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function Xe(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function N(e,t,n){if(n.length===0)return[];let r=new Set(n),i=tt(e,t),a=new Map,s=new Set;for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations)e.isIdentifier(o.name)&&s.add(o.name.text);let c=l=>{let o=kt(e,l);if(!o)return;let g=$e(i,o.root),f=g?a.get(g):void 0;return f?[...f,...o.segments.slice(1)]:ve(e,i,t,o.root)||s.has(o.root.text)?void 0:o.segments};for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations){if(!o.initializer||!e.isIdentifier(o.name))continue;let g=c(o.initializer),f=$e(i,o.name);!g||!f||a.set(f,g)}let p=[],y=new Set,u=(l,o)=>{let g=et(t,o),f=`${l}:${o.getStart(t)}`;y.has(f)||(y.add(f),p.push({name:l,line:g,node:o}))},m=l=>{let o=l.parent&&(e.isPropertyAccessExpression(l.parent)||e.isElementAccessExpression(l.parent))&&l.parent.expression===l;if((e.isPropertyAccessExpression(l)||e.isElementAccessExpression(l))&&!o){let g=c(l),f=g?Xe(r,g):void 0;f&&u(f,l)}else e.isIdentifier(l)&&r.has(l.text)&&Et(e,l)&&!ve(e,i,t,l)&&u(l.text,l);if(e.isVariableDeclaration(l)&&e.isObjectBindingPattern(l.name)&&l.initializer){let g=c(l.initializer);if(g)for(let f of l.name.elements){if(!e.isIdentifier(f.name))continue;let I=f.propertyName?j(e,f.propertyName)??f.propertyName.text:f.name.text,x=Xe(r,[...g,I]);x&&u(x,l.initializer)}}e.forEachChild(l,m)};return m(t),p}function _e(e,t){let n=[];for(let r of _(e,t)){if(r.typeOnly||!r.specifier)continue;let i=B(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of N(e,t,Le)){let i=se(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var Ne={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function M(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Me(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&M(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:Ne.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:Ne.PUBLISH_MISSING_SOURCE}),t}function b(e,t,n){return{ruleId:e,code:e,message:t,...n}}function V(e,t){return e.slice(0,t).split(`
|
|
2
2
|
`).length}function wt(e){let t=[],n=/['"`]([A-Za-z][A-Za-z0-9_.]*)['"`]/g,r;for(;(r=n.exec(e))!==null;)t.push({value:r[1],index:r.index});return t}function Lt(e){let t=[],n=[{kind:"import",re:/\bimport\s+(?:type\s+)?(?:[^'"]*?\s+from\s*)?['"]([^'"]+)['"]/g},{kind:"export",re:/\bexport\s+(?:type\s+)?[^'"]*?\s+from\s*['"]([^'"]+)['"]/g},{kind:"dynamic-import",re:/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g},{kind:"require",re:/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g}];for(let r of n){let i;for(;(i=r.re.exec(e))!==null;){let a=i.index+i[0].indexOf(i[1]),s=i[0],c=r.kind==="import"&&/\bimport\s+type\b/.test(s)||r.kind==="export"&&/\bexport\s+type\b/.test(s);t.push({value:i[1],index:a,kind:r.kind,typeOnly:c})}}return t.sort((r,i)=>r.index-i.index)}function Pt(e,t){let n=e.createSourceFile("generated.ts",t,e.ScriptTarget.Latest,!0),r=[],i=a=>{e.isStringLiteralLike(a)&&r.push({value:a.text,index:a.getStart(n)}),e.forEachChild(a,i)};return i(n),r}function Ot(e){let t=e.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);return["adapter","adapters","infra","infrastructure","persistence","repository","repositories","integration","database","db"].some(n=>t.includes(n))}function Rt(e){let t=e.toLowerCase();return["sequelize","prisma","typeorm","mongoose","knex"].some(n=>t===n||t.startsWith(`${n}/`))}function $t(e){let t=e.toLowerCase();return["adapter","infra","persistence","repository","repositories","integration","database"].some(n=>t.includes(n))}function K(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function vt(e,t){if(t&&(e.isIdentifier(t)||e.isStringLiteralLike(t)))return t.text}function nt(e,t,n){if(!(!t||!e.isObjectLiteralExpression(t)))return t.properties.find(r=>!e.isPropertyAssignment(r)&&!e.isShorthandPropertyAssignment(r)?!1:vt(e,r.name)===n)}function W(e,t,n){return nt(e,t,n)!==void 0}function q(e,t,n){let r=nt(e,t,n);return r&&e.isPropertyAssignment(r)?r.initializer:void 0}function _t(e,t){let n=q(e,t,"metadata");return W(e,n,"source")}function rt(e,t){return t?e.isIdentifier(t)?/^[A-Z]/.test(t.text):e.isPropertyAccessExpression(t)?rt(e,t.name):!1:!1}function Nt(e,t){if(!e.isCallExpression(t))return!1;let n=t.expression;return e.isPropertyAccessExpression(n)?n.name.text==="publish":e.isIdentifier(n)&&n.text==="publish"}function Mt(e,t){if(!e.isCallExpression(t))return!1;let n=t.arguments[0],r=K(e,n);return r!==void 0&&M(r)||W(e,n,"intent")||rt(e,n)}function Tt(e,t){if(!e.isCallExpression(t))return!1;let[n,r,i]=t.arguments;return _t(e,n)||W(e,r,"source")||W(e,i,"source")}function Dt(e,t){if(!e.isCallExpression(t))return;let[n,r,i]=t.arguments,a=q(e,n,"metadata");return K(e,q(e,a,"source"))??K(e,q(e,r,"source"))??K(e,q(e,i,"source"))}function Ft(e,t,n,r){let i=e.createSourceFile("generated.ts",t,e.ScriptTarget.Latest,!0),a=n,s=a?.filePath,c=a?.layer,p=[],y=m=>i.getLineAndCharacterOfPosition(m.getStart(i)).line+1,u=m=>{if(Nt(e,m)){let l=m.arguments[0],o=K(e,l);for(let f of Me({publishCall:!0,rawIntentName:o,objectHasIntent:W(e,l,"intent"),arkPublishCandidate:Mt(e,m),hasSource:Tt(e,m)}))p.push(b(f.ruleId,f.message,{line:y(m),filePath:s}));let g=Dt(e,m);if(r&&c&&g&&M(g)){let f=r.resolveLayer(g);f&&f!==c&&p.push(b("PUBLISH_SOURCE_LAYER_MISMATCH",`Publish source "${g}" resolves to ${f}, but the target file is classified as ${c}.`,{line:y(m),filePath:s,target:g,fromLayer:c,toLayer:f}))}}e.forEachChild(m,u)};return u(i),p}function Te(e={}){let t=new Set((e.intents||[]).map(a=>typeof a=="string"?a:a.name)),n=e.forbiddenPatterns||[],r=new Set(e.infrastructureLayers??[]),i=e.enforceIntentAllowlist??t.size>0;return{validate(a,s){let c=[],p=s,y=p?.filePath,u=p?.layer,m=e.typescript,l=m?m.createSourceFile(y??"generated.ts",a,m.ScriptTarget.Latest,!0):void 0,o=l?_(e.typescript,l):void 0,g=o?o.filter(d=>d.specifier!==void 0).map(d=>({value:d.specifier,index:d.node.getStart(l),kind:d.kind,typeOnly:d.typeOnly})):Lt(a),f=e.typescript?Pt(e.typescript,a):wt(a);if(e.typescript&&!e.allowNonLiteralDynamicImport?.(y))for(let d of o?.filter(({unresolved:h})=>h)??[]){let h=d.kind==="require";c.push(b(h?"DYNAMIC_REQUIRE_NOT_ALLOWLISTED":"DYNAMIC_IMPORT_NOT_ALLOWLISTED",`Non-literal ${h?"require call":"dynamic import"} cannot be resolved statically; add the reviewed file to dynamicImportAllowlist.`,{line:d.line,filePath:y}))}let I=u!==void 0&&(r.has(u)||$t(u)),x=u!==void 0?` If "${u}" is an infrastructure layer, mark it in ark.config.json with "mayImportInfrastructure": true (or name it with an infra token like Adapters/Persistence/Repository).`:"";for(let d of n)if(d instanceof RegExp){d.lastIndex=0;let h=d.exec(a);d.lastIndex=0,h&&c.push(b("FORBIDDEN_PATTERN",`Forbidden pattern matched: ${d}`,{line:h.index===void 0?void 0:V(a,h.index),filePath:y,suggestion:"Remove infrastructure imports from domain/application layers."+x}))}else a.includes(d)&&c.push(b("FORBIDDEN_SUBSTRING",`Forbidden substring: ${d}`,{line:V(a,a.indexOf(d)),filePath:y}));for(let d of g){let h=e.resolveImportTarget?.(d.value,y)??(e.resolveImportLayer?{layer:e.resolveImportLayer(d.value,y)}:void 0),A=typeof y=="string"?e.resolveImportTarget?.(y)??(e.resolveImportLayer?{layer:u,relPath:void 0}:void 0):void 0,F=h?.layer;if(F&&u){let ae=v(e.architectureProfile?.rules,u,F,{fromPath:A?.relPath,toPath:h?.relPath,layers:e.architectureLayers});if(ae){if(d.typeOnly&&!ae.peerIsolation)continue;let xe=!!ae.peerIsolation;c.push(b("LAYER_IMPORT_VIOLATION",ae.message??(xe?`Layer "${u}" must not import across slices into "${F}".`:`Layer "${u}" must not import "${F}".`),{line:V(a,d.index),source:d.value,target:d.value,filePath:y,fromLayer:u,toLayer:F,suggestion:xe?"Extract shared code to a shared layer, or coordinate slices via events/ports \u2014 do not import across feature/context slices.":"Depend on a port/interface owned by an inner layer instead, or move this code to a layer allowed to make this import.",details:{importKind:d.kind,peerIsolation:xe,...d.typeOnly?{typeOnly:!0}:{}}}));continue}if(F!==u)continue}I||d.typeOnly||!Ot(d.value)&&!Rt(d.value)||c.push(b("FORBIDDEN_IMPORT",`Forbidden ${d.kind} target: "${d.value}".`,{line:V(a,d.index),source:d.value,target:d.value,filePath:y,suggestion:"Route infrastructure access through an allowed adapter or port boundary."+x,details:{importKind:d.kind}}))}if(e.policies)for(let d of e.policies){let h=d.check({source:a,context:s});if(h!==!0)if(Array.isArray(h))for(let A of h)c.push(b("POLICY_VIOLATION",A.message,{filePath:y,suggestion:`Fix violation of policy "${d.name}".`}));else h===!1?c.push(b("POLICY_VIOLATION",`Policy ${d.name} failed on generated code`)):c.push(b("POLICY_VIOLATION",h.message))}if(i&&t.size>0)for(let d of f)M(d.value)&&!t.has(d.value)&&c.push(b("UNKNOWN_INTENT",`Unknown intent reference: "${d.value}"`,{line:V(a,d.index),filePath:y,target:d.value,suggestion:`Register intent "${d.value}" via defineIntent() or remove the reference.`}));if(e.architectureProfile&&u)for(let d of f){if(!M(d.value))continue;let h=e.architectureProfile.resolveLayer(d.value);if(!h)continue;let A=v(e.architectureProfile.rules,u,h);A&&c.push(b("LAYER_REFERENCE_VIOLATION",A.message??`Layer "${u}" must not reference "${h}" through "${d.value}".`,{line:V(a,d.index),filePath:y,target:d.value,fromLayer:u,toLayer:h,suggestion:"Route the dependency through an allowed intent, port, or event.",details:{rule:A}}))}if(e.extensions)for(let d of e.extensions)try{let h=d.analyze(a,s);c.push(...h)}catch(h){c.push(b("EXTENSION_ERROR",`Extension "${d.name}" failed: ${h instanceof Error?h.message:String(h)}`))}if(e.typescript&&l&&u&&e.forbiddenGlobals?.[u]?.length)try{c.push(...N(e.typescript,l,e.forbiddenGlobals[u]).map(d=>b("FORBIDDEN_GLOBAL",`${u} must not use the ambient global "${d.name}".`,{line:d.line,filePath:y,target:d.name,fromLayer:u,suggestion:"Inject the capability through a port (e.g. a Clock, IdGenerator, or HttpPort) instead of reaching for the ambient global."})))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}if(e.typescript&&l&&u&&e.capabilityWalls?.[u]?.length)try{let d=new Set(e.capabilityWalls[u]),h=e.forbiddenGlobals?.[u]??[];for(let A of _e(e.typescript,l))d.has(A.capability)&&(A.source==="ambient-global"&&Pe(A.symbol,h)||c.push(b("CAPABILITY_VIOLATION",A.source==="import-based"?`${u} denies the ${A.capability} capability; found import of "${A.symbol}".`:`${u} denies the ${A.capability} capability; found ambient "${A.symbol}".`,{line:A.line,filePath:y,target:A.symbol,capability:A.capability,fromLayer:u,suggestion:"Define a small port (ClockPort, HttpPort, StoragePort) and bind the implementation in an adapter layer."})))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}if(e.typescript)try{c.push(...Ft(e.typescript,a,s,e.architectureProfile))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}return{valid:c.length===0,violations:c}}}}var at="1.0",de="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",it=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],jt=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function Vt(){let e=[];for(let t of it)for(let n of it)t===n||jt.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var ue=Vt();var R={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},fe={$schema:"https://json-schema.org/draft/2020-12/schema",$id:de,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:de,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...R,minItems:1,default:["src"]},exclude:{...R,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:ue,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...R,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...R,minItems:1},exclude:R,intentPrefixes:R,description:{type:"string",minLength:1},forbiddenGlobals:R,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...R,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},H=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
|
|
3
3
|
${n.map(r=>`- ${r.path}: ${r.message}`).join(`
|
|
4
4
|
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function ot(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function De(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function G(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Gt(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function pe(e,t,n,r,i){if(t.$ref){let a=Gt(t.$ref,r);if(!a){i.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}pe(e,a,n,r,i);return}if(t.const!==void 0&&!Object.is(e,t.const)){i.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(a=>Object.is(a,e))){i.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!ot(e)){i.push({path:n,message:`must be an object; received ${G(e)}`});return}let a=t.properties??{};for(let s of t.required??[])e[s]===void 0&&i.push({path:De(n,s),message:"is required"});if(t.additionalProperties===!1)for(let s of Object.keys(e))s in a||i.push({path:De(n,s),message:"unknown field"});for(let[s,c]of Object.entries(a))e[s]!==void 0&&pe(e[s],c,De(n,s),r,i);return}if(t.type==="array"){if(!Array.isArray(e)){i.push({path:n,message:`must be an array; received ${G(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&i.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let a=e.map(s=>JSON.stringify(s));new Set(a).size!==a.length&&i.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((a,s)=>pe(a,t.items,`${n}[${s}]`,r,i));return}if(t.type==="string"){if(typeof e!="string"){i.push({path:n,message:`must be a string; received ${G(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&i.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&i.push({path:n,message:`must be a boolean; received ${G(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){i.push({path:n,message:`must be an integer; received ${G(e)}`});return}t.minimum!==void 0&&e<t.minimum&&i.push({path:n,message:`must be at least ${t.minimum}`})}}function Ht(e){return{...e,$schema:e.$schema===void 0?de:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?ue.map(t=>({...t})):e.rules}}function Ut(e,t="ark.config.json"){if(!ot(e))throw new H(t,[{path:"$",message:`must be an object; received ${G(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new H(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:Ht(e),migratedFrom:n}}function J(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=Ut(e,t),i=[];if(pe(n,fe,"$",fe,i),i.length>0)throw new H(t,i);return{config:n,migratedFrom:r}}function ye(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new H(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return J(n,t)}function st(e){let t={$schema:typeof e.$schema=="string"&&e.$schema.length>0?e.$schema:de,schemaVersion:"1.0"};for(let[n,r]of Object.entries(e))n!=="$schema"&&n!=="schemaVersion"&&(t[n]=r);return t}function Bt(e){return e.endsWith(".")?e:`${e}.`}function zt(e,t){let n=e.prefixes.length?Math.max(...e.prefixes.map(i=>i.length)):0;return(t.prefixes.length?Math.max(...t.prefixes.map(i=>i.length)):0)-n}function Q(e){let t=e.layers.map(i=>({...i,prefixes:i.prefixes.map(Bt)})),n=[...t].sort(zt),r=[...e.rules??[]];return{name:e.name,layers:t,rules:r,resolveLayer(i){return t.find(a=>a.match?.(i))?.name??n.find(a=>a.prefixes.some(s=>i.startsWith(s)))?.name}}}function Fe(e,t={}){return Q({name:t.name??e.name??"ark.config.json",layers:e.layers.map((n,r)=>({name:n.name,prefixes:n.intentPrefixes??[],description:n.description,order:r+1})),rules:e.rules??[]})}var Yt=[{name:"DomainModel",prefixes:["Domain"],description:"Rich domain model, business rules, and domain events.",order:1},{name:"ApplicationOrchestration",prefixes:["Application"],description:"Use cases and command orchestration.",order:2},{name:"PersistenceAdapters",prefixes:["Adapter.Persistence","Adapter.Repository"],description:"Database, repository, and storage adapters.",order:3},{name:"IntegrationAdapters",prefixes:["Adapter.Integration","Adapter.External"],description:"External systems, APIs, and integration adapters.",order:4},{name:"WorkflowSagaEngine",prefixes:["Workflow"],description:"Sagas, workflows, and long-running processes.",order:5},{name:"BackgroundJobsScheduling",prefixes:["Job"],description:"Background jobs, scheduled work, and async processors.",order:6},{name:"PresentationAdapters",prefixes:["Presentation","Adapter.Presentation","Adapter.Api"],description:"API, UI, controller, and presentation adapters.",order:7},{name:"ReportingReadModels",prefixes:["Reporting"],description:"Read models, projections, and reporting surfaces.",order:8},{name:"ExtensibilityMetadata",prefixes:["Metadata"],description:"Metadata, extensions, and schema contracts.",order:9},{name:"SecurityAuditObservability",prefixes:["Security","Audit","Observability"],description:"Security, audit, and observability concerns.",order:10},{name:"Kernel",prefixes:["Kernel"],description:"Ark-owned governance and kernel signals.",order:11}],Z=Q({name:"Ark 11-layer Hexagonal Event-Driven Profile",layers:Yt,rules:ue.map(e=>({...e}))}),qt={DomainModel:["domain"],ApplicationOrchestration:["application","app"],PersistenceAdapters:["adapters/persistence","adapters/repository","repositories","infra/persistence"],IntegrationAdapters:["adapters/integration","adapters/external","integrations"],WorkflowSagaEngine:["workflows","sagas"],BackgroundJobsScheduling:["jobs","schedules"],PresentationAdapters:["presentation","adapters/presentation","adapters/api"],ReportingReadModels:["reporting","read-models","projections"],ExtensibilityMetadata:["metadata","extensions"],SecurityAuditObservability:["security","audit","observability"],Kernel:["kernel"]};function je(e={}){let t=e.rootDir??"src",n=e.optionalLayers??!0,r=t==="."?"":`${t}/`;return st({include:e.include??[t],layers:Z.layers.map(i=>({name:i.name,patterns:(qt[i.name]??[i.name]).map(a=>`${r}${a}/**`),intentPrefixes:i.prefixes,optional:n})),rules:[...Z.rules]})}var ge="1.0";function P(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function L(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(L).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(n=>`${JSON.stringify(n)}:${L(t[n])}`).join(",")}}`}function E(e){return`${e.from}->${e.to}`}function X(e,t,n,r="dependency"){return{id:`${e}:${r}:${E(t)}`,classification:e,subject:"dependency",from:t.from,to:t.to,message:n,...e==="missing"?{nextAction:`Add the planned dependency ${E(t)} to the candidate, then preflight again.`}:e==="contradictory"?{nextAction:`Replace the reverse dependency with ${E(t)}, then preflight again.`}:e==="unplanned"?{nextAction:`Remove the unplanned dependency ${E(t)} from the candidate, then preflight again.`}:{}}}function ee(e){let t=[],n=new Map(e.changeMap.map.files.map(l=>[l.path,l])),r=new Map(e.changes.map(l=>[l.path,l])),i=new Map(e.changeMap.map.dependencies.map(l=>[E(l),l])),a=new Map(e.baseDependencies.map(l=>[E(l),l])),s=new Map(e.candidateDependencies.map(l=>[E(l),l]));for(let l of[...n.values()].sort((o,g)=>o.path.localeCompare(g.path))){let o=r.get(l.path);o?o.operation!==l.operation?t.push({id:`contradictory:file:${l.path}`,classification:"contradictory",subject:"file",path:l.path,expectedOperation:l.operation,actualOperation:o.operation,message:`${l.path} was planned as ${l.operation} but the actual operation is ${o.operation}.`,nextAction:`Change ${l.path} to the planned ${l.operation} operation, then preflight again.`}):t.push({id:`satisfied:file:${l.path}`,classification:"satisfied",subject:"file",path:l.path,expectedOperation:l.operation,actualOperation:o.operation,message:`${l.path} matches the planned ${l.operation} operation.`}):t.push({id:`missing:file:${l.path}`,classification:"missing",subject:"file",path:l.path,expectedOperation:l.operation,message:`${l.path} was planned as ${l.operation} but is absent from the actual change.`,nextAction:`${l.operation[0].toUpperCase()}${l.operation.slice(1)} ${l.path} in the complete change set, then preflight again.`})}for(let l of[...r.values()].sort((o,g)=>o.path.localeCompare(g.path)))n.has(l.path)||t.push({id:`unplanned:file:${l.path}`,classification:"unplanned",subject:"file",path:l.path,actualOperation:l.operation,message:`${l.path} has an unplanned ${l.operation} operation.`,nextAction:`Remove ${l.path} from the change set, then preflight again.`});let c=new Set;for(let l of[...i.values()].sort((o,g)=>E(o).localeCompare(E(g)))){if(s.has(E(l))){t.push(X("satisfied",l,`${l.from} -> ${l.to} exists in the candidate architecture.`));continue}let o={from:l.to,to:l.from};s.has(E(o))?(c.add(E(o)),t.push(X("contradictory",l,`${l.from} -> ${l.to} was planned, but the candidate contains the reverse edge.`))):t.push(X("missing",l,`${l.from} -> ${l.to} is absent from the candidate architecture.`))}let p=new Set([...n.keys(),...r.keys()]);for(let[l,o]of[...s].sort(([g],[f])=>g.localeCompare(f)))a.has(l)||i.has(l)||c.has(l)||!p.has(o.from)&&!p.has(o.to)||t.push({...X("unplanned",o,`${o.from} -> ${o.to} was added without a matching planned dependency.`,"dependency-added"),actualOperation:"added"});let y=new Set(e.changeMap.map.files.filter(l=>l.operation==="delete").map(l=>l.path));for(let[l,o]of[...a].sort(([g],[f])=>g.localeCompare(f)))s.has(l)||y.has(o.from)||y.has(o.to)||!p.has(o.from)&&!p.has(o.to)||t.push({...X("unplanned",o,`${o.from} -> ${o.to} was removed without a planned file deletion.`,"dependency-removed"),actualOperation:"removed"});let u={satisfied:0,missing:1,contradictory:2,unplanned:3};t.sort((l,o)=>u[l.classification]-u[o.classification]||(l.subject===o.subject?0:l.subject==="file"?-1:1)||l.id.localeCompare(o.id));let m={satisfied:t.filter(l=>l.classification==="satisfied").length,missing:t.filter(l=>l.classification==="missing").length,contradictory:t.filter(l=>l.classification==="contradictory").length,unplanned:t.filter(l=>l.classification==="unplanned").length};return{schemaVersion:"1.0",readOnly:!0,changeMapHash:e.changeMap.hash,structurallyConverged:m.missing===0&&m.contradictory===0&&m.unplanned===0,behavioralCompletion:"not-evaluated",summary:m,findings:t}}var Ae="1.0";function C(e,t){e.push({id:`${t.classification}:${t.path}:${t.kind}`,path:t.path,classification:t.classification,message:t.message,...t.classification==="weakening"||t.classification==="judgment-required"?{nextAction:`Restore the previous protection at ${t.path}, then run ArkGate again.`}:{},...t.before===void 0?{}:{before:t.before},...t.after===void 0?{}:{after:t.after}})}function w(e){return[...new Set(e??[])].sort()}function T(e,t,n,r,i){let a=w(n),s=w(r),c=new Set(a),p=new Set(s),y=s.filter(m=>!c.has(m)),u=a.filter(m=>!p.has(m));y.length===0&&u.length===0||(y.length>0&&C(e,{kind:"added",path:t,classification:i.added,message:i.addedMessage,before:a,after:s}),u.length>0&&C(e,{kind:"removed",path:t,classification:i.removed,message:i.removedMessage,before:a,after:s}))}function me(e,t,n,r,i,a,s){if(n===r)return;C(e,{kind:r?"enabled":"disabled",path:t,classification:r?i:i==="strengthening"?"weakening":"strengthening",message:r?a:s,before:n,after:r})}function he(e,t){let n=new Map,r=new Set;for(let i of e){let a=t(i);n.has(a)?r.add(a):n.set(a,i)}return{values:n,duplicates:[...r].sort()}}function Kt(e,t,n){let r=he(t,a=>a.name),i=he(n,a=>a.name);(r.duplicates.length>0||i.duplicates.length>0)&&C(e,{kind:"duplicate-layer",path:"$.layers",classification:"judgment-required",message:"Duplicate layer names make policy ownership ambiguous.",before:r.duplicates,after:i.duplicates});for(let a of[...new Set([...r.values.keys(),...i.values.keys()])].sort()){let s=r.values.get(a),c=i.values.get(a),p=`$.layers[${a}]`;if(!s&&c){C(e,{kind:"layer-added",path:p,classification:"judgment-required",message:"A layer was added; verify overlap, ownership, and rule coverage.",after:c});continue}if(s&&!c){C(e,{kind:"layer-removed",path:p,classification:"weakening",message:"Removing a layer can leave its source paths ungoverned.",before:s});continue}if(!s||!c)continue;T(e,`${p}.patterns`,s.patterns,c.patterns,{added:"strengthening",removed:"weakening",addedMessage:"Additional paths are governed by this layer.",removedMessage:"Paths were removed from this layer and may become ungoverned."}),T(e,`${p}.exclude`,s.exclude,c.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional paths are excluded from this layer.",removedMessage:"Fewer paths are excluded from this layer."});let y=le(s),u=le(c);T(e,`${p}.forbiddenGlobals`,y.rawGlobals,u.rawGlobals,{added:"strengthening",removed:"weakening",addedMessage:"Additional forbidden globals are enforced in this layer.",removedMessage:"A forbidden-global protection was removed from this layer."}),T(e,`${p}.capabilities`,y.atoms,u.atoms,{added:"strengthening",removed:"weakening",addedMessage:"Additional ambient/import protection is enforced in this layer (coverage atoms).",removedMessage:"An ambient/import protection was lost from this layer (coverage atoms)."}),w(s.intentPrefixes).join("\0")!==w(c.intentPrefixes).join("\0")&&C(e,{kind:"intent-prefixes-changed",path:`${p}.intentPrefixes`,classification:"judgment-required",message:"Intent ownership changed and must be reviewed against publishers and consumers.",before:w(s.intentPrefixes),after:w(c.intentPrefixes)}),me(e,`${p}.mayImportInfrastructure`,s.mayImportInfrastructure===!0,c.mayImportInfrastructure===!0,"weakening","The layer may now import infrastructure directly.","Direct infrastructure imports are no longer allowed for this layer."),me(e,`${p}.optional`,s.optional===!0,c.optional===!0,"weakening","The layer is now optional and can be absent without a strict warning.","The layer is now required when its contract is active.")}}function Wt(e,t,n){let r=s=>`${s.from}->${s.to}`,i=he(t,r),a=he(n,r);(i.duplicates.length>0||a.duplicates.length>0)&&C(e,{kind:"duplicate-rule",path:"$.rules",classification:"judgment-required",message:"Duplicate rule edges make the effective verdict order-dependent.",before:i.duplicates,after:a.duplicates});for(let s of[...new Set([...i.values.keys(),...a.values.keys()])].sort()){let c=i.values.get(s),p=a.values.get(s),y=`$.rules[${s}]`;if(!c&&p){p.allowed===!1&&C(e,{kind:"deny-added",path:y,classification:"strengthening",message:"A denied dependency edge was added.",after:p});continue}if(c&&!p){c.allowed===!1&&C(e,{kind:"deny-removed",path:y,classification:"weakening",message:"A denied dependency edge was removed.",before:c});continue}if(!c||!p)continue;c.allowed!==p.allowed&&C(e,{kind:p.allowed?"deny-disabled":"deny-enabled",path:`${y}.allowed`,classification:p.allowed?"weakening":"strengthening",message:p.allowed?"A previously denied dependency edge is now allowed.":"A dependency edge is now denied.",before:c.allowed,after:p.allowed});let u=c.peerIsolation===!0,m=p.peerIsolation===!0;if(u!==m){let l=c.from===c.to&&p.from===p.to;C(e,{kind:m?"peer-isolation-enabled":"peer-isolation-disabled",path:`${y}.peerIsolation`,classification:l?m?"strengthening":"weakening":"judgment-required",message:l?m?"Cross-slice dependencies inside this layer are now denied.":"Cross-slice dependencies inside this layer are no longer denied.":"Changing peer isolation on a cross-layer edge changes the denial scope.",before:u,after:m})}w(c.sliceFolders).join("\0")!==w(p.sliceFolders).join("\0")&&C(e,{kind:"slice-folders-changed",path:`${y}.sliceFolders`,classification:"judgment-required",message:"Slice ownership folders changed and can reclassify existing dependencies.",before:w(c.sliceFolders),after:w(p.sliceFolders)})}}function Jt(e,t,n){let r=t.safety??{},i=n.safety??{};for(let a of["maxTsSuppressions","maxAnyCasts"]){let s=r[a]??0,c=i[a]??0;s!==c&&C(e,{kind:c>s?"threshold-raised":"threshold-lowered",path:`$.safety.${a}`,classification:c>s?"weakening":"strengthening",message:c>s?"The safety threshold allows more violations.":"The safety threshold allows fewer violations.",before:s,after:c})}for(let a of["allowInMemory","allowDisabledPeerIsolation"])me(e,`$.safety.${a}`,r[a]===!0,i[a]===!0,"weakening","A safety exception was enabled.","A safety exception was disabled.")}function Zt(e){return e.some(t=>t.classification==="weakening")?"weakening":e.some(t=>t.classification==="judgment-required")?"judgment-required":e.some(t=>t.classification==="strengthening")?"strengthening":"neutral"}function be(e,t){let n=[];T(n,"$.include",e.include,t.include,{added:"strengthening",removed:"weakening",addedMessage:"Additional project roots are governed.",removedMessage:"Project roots were removed from governance."}),T(n,"$.exclude",e.exclude,t.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional project paths are excluded from governance.",removedMessage:"Fewer project paths are excluded from governance."}),T(n,"$.dynamicImportAllowlist",e.dynamicImportAllowlist,t.dynamicImportAllowlist,{added:"weakening",removed:"strengthening",addedMessage:"Additional files may use non-literal dynamic imports.",removedMessage:"Fewer files may use non-literal dynamic imports."}),me(n,"$.excludeGenerated",e.excludeGenerated!==!1,t.excludeGenerated!==!1,"weakening","Generated source is now excluded from governance.","Generated source is now governed.");let r={off:0,soft:1,"framework-soft":1,strict:2},i=e.cyclePolicy??"strict",a=t.cyclePolicy??"strict";if(i!==a){let s=r[a]===r[i]?"judgment-required":r[a]>r[i]?"strengthening":"weakening";C(n,{kind:"cycle-policy-changed",path:"$.cyclePolicy",classification:s,message:"The cycle enforcement level changed.",before:i,after:a})}return(e.frameworkOverlay??null)!==(t.frameworkOverlay??null)&&C(n,{kind:"framework-overlay-changed",path:"$.frameworkOverlay",classification:"judgment-required",message:"The framework overlay changed and may alter effective layer matching.",before:e.frameworkOverlay??null,after:t.frameworkOverlay??null}),Kt(n,e.layers,t.layers),Wt(n,e.rules,t.rules),Jt(n,e,t),n.sort((s,c)=>s.path.localeCompare(c.path)||s.id.localeCompare(c.id)),{schemaVersion:Ae,classification:Zt(n),findings:n}}function Ce(e,t){if(!e||e.schemaVersion!==Ae||typeof e.basePolicyHash!="string"||typeof e.candidatePolicyHash!="string"||typeof e.reason!="string"||!Array.isArray(e.findingIds)||e.findingIds.some(i=>typeof i!="string")||e.reason.trim().length===0||e.basePolicyHash!==t.basePolicyHash||e.candidatePolicyHash!==t.candidatePolicyHash)return!1;let n=w(e.findingIds),r=w(t.findingIds);return n.length===r.length&&n.every((i,a)=>i===r[a])}function D(e){let t=[];for(let n of e.replace(/\\/g,"/").split("/"))!n||n==="."||(n===".."&&t.length>0&&t.at(-1)!==".."?t.pop():t.push(n));return t.join("/")}function ct(e){return e!==void 0&&/[A-Za-z0-9_$]/.test(e)}function $(e,t){for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t}function te(e,t){let n=e[t];if(n!=="'"&&n!=='"')return;let r=t,i="";for(t+=1;t<e.length;t+=1){let a=e[t];if(a===n)return{value:i,offset:r,excerpt:e.slice(r,t+1)};a==="\\"&&t+1<e.length?(i+=e[t+1],t+=1):i+=a}}function O(e,t,n){return e.startsWith(t,n)&&!ct(e[n-1])&&!ct(e[n+t.length])}function Qt(e,t){if(t=$(e,t+6),e[t]==="(")return te(e,$(e,t+1));let n=!1;if(O(e,"type",t)){let i=$(e,t+4);e[i]!==","&&!O(e,"from",i)&&(n=!0)}let r=lt(e,t,!0);return r&&n?{...r,typeOnly:!0}:r}function Xt(e,t){t=t+6;let n=$(e,t),r=!1;if(O(e,"type",n)){let a=$(e,n+4);(e[a]==="{"||e[a]==="*")&&(r=!0)}let i=lt(e,t,!1);return i&&r?{...i,typeOnly:!0}:i}function lt(e,t,n){for(;t<e.length;t+=1){if(e[t]===";")return;if(O(e,"from",t))return te(e,$(e,t+4));if(n&&(e[t]==="'"||e[t]==='"'))return te(e,t);if(t>0&&(O(e,"import",t)||O(e,"export",t)))return}}function en(e,t){for(t+=1;t<e.length;t+=1){let n=e[t];if(n==="\\")t+=1;else if(n==="`")return t}return e.length}function tn(e,t){let n=t-1;for(;n>=0&&/\s/.test(e[n]);)n-=1;if(e[n]===".")return;let r=$(e,t+7);if(e[r]!=="(")return;r=$(e,r+1);let i=te(e,r);return i?{...i,requireCall:!0}:void 0}function nn(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="/"&&e[n+1]==="/"){if(n=e.indexOf(`
|
package/dist/index.d.cts
CHANGED
|
@@ -234,7 +234,7 @@ declare function loadArkConfigContract(input: unknown, source?: string): ArkConf
|
|
|
234
234
|
declare function parseArkConfigJson(json: string, source?: string): ArkConfigLoadResult;
|
|
235
235
|
|
|
236
236
|
/** ArkGate library version — single source of truth. */
|
|
237
|
-
declare const version = "3.
|
|
237
|
+
declare const version = "3.6.0";
|
|
238
238
|
|
|
239
239
|
/** Versioned public result contract shared by every ArkGate enforcement adapter. */
|
|
240
240
|
declare const ARK_ANALYSIS_RESULT_SCHEMA_VERSION: "1.1";
|
package/dist/index.d.ts
CHANGED
|
@@ -234,7 +234,7 @@ declare function loadArkConfigContract(input: unknown, source?: string): ArkConf
|
|
|
234
234
|
declare function parseArkConfigJson(json: string, source?: string): ArkConfigLoadResult;
|
|
235
235
|
|
|
236
236
|
/** ArkGate library version — single source of truth. */
|
|
237
|
-
declare const version = "3.
|
|
237
|
+
declare const version = "3.6.0";
|
|
238
238
|
|
|
239
239
|
/** Versioned public result contract shared by every ArkGate enforcement adapter. */
|
|
240
240
|
declare const ARK_ANALYSIS_RESULT_SCHEMA_VERSION: "1.1";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var ct="3.5.0";var lt="1.1";function k(e){return typeof e=="string"&&e.length>0?e:void 0}function Te(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function pt(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${k(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ge(e,t="error"){let n=k(e.ruleId)??k(e.code)??"ARK_UNKNOWN",r=e.severity==="warning"?"warning":t,i={...k(e.target)?{target:k(e.target)}:{},...k(e.fromLayer)?{fromLayer:k(e.fromLayer)}:{},...k(e.toLayer)?{toLayer:k(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:r,message:k(e.message)??n,location:{file:k(e.file)??"<unknown>",line:Te(e.line,1),column:Te(e.column,1)},evidence:i,nextAction:k(e.nextAction)??pt(n,i,e)}}function dt(e){return{schemaVersion:"1.1",valid:e.valid,diagnostics:[...(e.violations??[]).map(t=>ge(t,"error")),...(e.warnings??[]).map(t=>ge(t,"warning"))]}}var ft={$schema:"https://json-schema.org/draft/2020-12/schema",$id:"https://unpkg.com/arkgate@2/schemas/ark.analysis-result.schema.json",title:"ArkGate analysis result",type:"object",additionalProperties:!1,required:["schemaVersion","valid","diagnostics"],properties:{schemaVersion:{const:"1.1"},valid:{type:"boolean"},diagnostics:{type:"array",items:{type:"object",additionalProperties:!1,required:["ruleId","severity","message","location","evidence"],properties:{ruleId:{type:"string",minLength:1},severity:{enum:["error","warning"]},message:{type:"string",minLength:1},location:{type:"object",additionalProperties:!1,required:["file","line","column"],properties:{file:{type:"string",minLength:1},line:{type:"integer",minimum:1},column:{type:"integer",minimum:1}}},evidence:{type:"object",additionalProperties:!1,properties:{target:{type:"string"},fromLayer:{type:"string"},toLayer:{type:"string"},typeOnly:{type:"boolean"}}},nextAction:{type:"string",minLength:1}}}}}};var he=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Ae=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),be=Object.freeze(Object.keys(Ae).sort()),me=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function U(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=me[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=me[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:me[e.slice(0,a)]??null}function X(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=Ae[r];if(i)return i}return null}function ee(e){if(e?.pure===!0)return[...he].sort();let n=(e?.capabilities?.deny??[]).filter(r=>he.includes(r));return[...new Set(n)].sort()}function Ce(e,t){if(t.length===0)return!1;let n=new Set(t),r=e.split(".");for(let i=r.length;i>=1;i-=1)if(n.has(r.slice(0,i).join(".")))return!0;return!1}function te(e){let t=new Set,n=new Set,r=Object.keys(Ae);for(let i of e?.forbiddenGlobals??[]){let a=r.filter(s=>s===i||s.startsWith(`${i}.`));if(a.length===0)n.add(i);else for(let s of a)t.add(`ambient:${s}`)}for(let i of ee(e)){t.add(`import:${i}`);for(let a of r)X(a)===i&&t.add(`ambient:${a}`)}return{atoms:[...t].sort(),rawGlobals:[...n].sort()}}var De=new Map;function Fe(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function Ie(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function ut(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function B(e){let t=De.get(e);if(t)return t;let n=Ie(e),r=ut(n),i="",a=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(i+=Fe(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(i+="(?:.*/)?",c+=2):(i+=".*",c+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=Fe(p)}let s=new RegExp(`^${i}$`);return De.set(e,s),s}function xe(e){let t=Ie(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function z(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(s=>B(s).test(n))){for(let s of a.patterns??[])if(B(s).test(n)){let c=xe(s);c>i&&(i=c,r=a.name)}}return r}function je(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function yt(e){let t=new Set;for(let n of e??[]){let i=Ie(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let s=i[a];if((s==="**"||s==="*")&&a>0){let c=i[a-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function gt(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return yt(r?.patterns)}function v(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,s=r?.toPath;if(!a||!s)continue;let c=gt(i,t,r?.layers);if(c.length===0)continue;let p=je(a,c),y=je(s,c);if(!p||!y)continue;if(p!==y)return i;continue}if(t!==n)return i}}function D(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function He(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Ve(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function Ue(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function Se(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ke(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):Se(t,r)}catch{a=void 0}return!!a?.declarations?.some(s=>s.getSourceFile().fileName===n.fileName)}function F(e,t){let n,r=[],i=(s,c,p,y=!1)=>r.push({specifier:p,kind:c,line:He(t,s),typeOnly:y,unresolved:p===void 0,node:s}),a=s=>{if(e.isImportDeclaration(s))i(s,"import",D(e,s.moduleSpecifier),Ve(e,s));else if(e.isExportDeclaration(s)&&s.moduleSpecifier)i(s,"export",D(e,s.moduleSpecifier),Ve(e,s));else if(e.isImportEqualsDeclaration(s)&&e.isExternalModuleReference(s.moduleReference))i(s,"require",D(e,s.moduleReference.expression));else if(e.isCallExpression(s)){let c=s.expression.kind===e.SyntaxKind.ImportKeyword,y=e.isIdentifier(s.expression)&&s.expression.text==="require"&&!ke(e,n??(n=Ue(e,t)),t,s.expression);(c||y)&&i(s,y?"require":"dynamic-import",D(e,s.arguments[0]))}e.forEachChild(s,a)};return a(t),r}function mt(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=D(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function ht(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function Ge(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function j(e,t,n){if(n.length===0)return[];let r=new Set(n),i=Ue(e,t),a=new Map,s=new Set;for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations)e.isIdentifier(o.name)&&s.add(o.name.text);let c=l=>{let o=mt(e,l);if(!o)return;let g=Se(i,o.root),f=g?a.get(g):void 0;return f?[...f,...o.segments.slice(1)]:ke(e,i,t,o.root)||s.has(o.root.text)?void 0:o.segments};for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations){if(!o.initializer||!e.isIdentifier(o.name))continue;let g=c(o.initializer),f=Se(i,o.name);!g||!f||a.set(f,g)}let p=[],y=new Set,u=(l,o)=>{let g=He(t,o),f=`${l}:${o.getStart(t)}`;y.has(f)||(y.add(f),p.push({name:l,line:g,node:o}))},m=l=>{let o=l.parent&&(e.isPropertyAccessExpression(l.parent)||e.isElementAccessExpression(l.parent))&&l.parent.expression===l;if((e.isPropertyAccessExpression(l)||e.isElementAccessExpression(l))&&!o){let g=c(l),f=g?Ge(r,g):void 0;f&&u(f,l)}else e.isIdentifier(l)&&r.has(l.text)&&ht(e,l)&&!ke(e,i,t,l)&&u(l.text,l);if(e.isVariableDeclaration(l)&&e.isObjectBindingPattern(l.name)&&l.initializer){let g=c(l.initializer);if(g)for(let f of l.name.elements){if(!e.isIdentifier(f.name))continue;let I=f.propertyName?D(e,f.propertyName)??f.propertyName.text:f.name.text,x=Ge(r,[...g,I]);x&&u(x,l.initializer)}}e.forEachChild(l,m)};return m(t),p}function Ee(e,t){let n=[];for(let r of F(e,t)){if(r.typeOnly||!r.specifier)continue;let i=U(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of j(e,t,be)){let i=X(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var we={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function _(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Le(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&_(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:we.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:we.PUBLISH_MISSING_SOURCE}),t}function b(e,t,n){return{ruleId:e,code:e,message:t,...n}}function V(e,t){return e.slice(0,t).split(`
|
|
1
|
+
var ct="3.6.0";var lt="1.1";function k(e){return typeof e=="string"&&e.length>0?e:void 0}function Te(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function pt(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${k(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ge(e,t="error"){let n=k(e.ruleId)??k(e.code)??"ARK_UNKNOWN",r=e.severity==="warning"?"warning":t,i={...k(e.target)?{target:k(e.target)}:{},...k(e.fromLayer)?{fromLayer:k(e.fromLayer)}:{},...k(e.toLayer)?{toLayer:k(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:r,message:k(e.message)??n,location:{file:k(e.file)??"<unknown>",line:Te(e.line,1),column:Te(e.column,1)},evidence:i,nextAction:k(e.nextAction)??pt(n,i,e)}}function dt(e){return{schemaVersion:"1.1",valid:e.valid,diagnostics:[...(e.violations??[]).map(t=>ge(t,"error")),...(e.warnings??[]).map(t=>ge(t,"warning"))]}}var ft={$schema:"https://json-schema.org/draft/2020-12/schema",$id:"https://unpkg.com/arkgate@2/schemas/ark.analysis-result.schema.json",title:"ArkGate analysis result",type:"object",additionalProperties:!1,required:["schemaVersion","valid","diagnostics"],properties:{schemaVersion:{const:"1.1"},valid:{type:"boolean"},diagnostics:{type:"array",items:{type:"object",additionalProperties:!1,required:["ruleId","severity","message","location","evidence"],properties:{ruleId:{type:"string",minLength:1},severity:{enum:["error","warning"]},message:{type:"string",minLength:1},location:{type:"object",additionalProperties:!1,required:["file","line","column"],properties:{file:{type:"string",minLength:1},line:{type:"integer",minimum:1},column:{type:"integer",minimum:1}}},evidence:{type:"object",additionalProperties:!1,properties:{target:{type:"string"},fromLayer:{type:"string"},toLayer:{type:"string"},typeOnly:{type:"boolean"}}},nextAction:{type:"string",minLength:1}}}}}};var he=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Ae=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),be=Object.freeze(Object.keys(Ae).sort()),me=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function U(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=me[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=me[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:me[e.slice(0,a)]??null}function X(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=Ae[r];if(i)return i}return null}function ee(e){if(e?.pure===!0)return[...he].sort();let n=(e?.capabilities?.deny??[]).filter(r=>he.includes(r));return[...new Set(n)].sort()}function Ce(e,t){if(t.length===0)return!1;let n=new Set(t),r=e.split(".");for(let i=r.length;i>=1;i-=1)if(n.has(r.slice(0,i).join(".")))return!0;return!1}function te(e){let t=new Set,n=new Set,r=Object.keys(Ae);for(let i of e?.forbiddenGlobals??[]){let a=r.filter(s=>s===i||s.startsWith(`${i}.`));if(a.length===0)n.add(i);else for(let s of a)t.add(`ambient:${s}`)}for(let i of ee(e)){t.add(`import:${i}`);for(let a of r)X(a)===i&&t.add(`ambient:${a}`)}return{atoms:[...t].sort(),rawGlobals:[...n].sort()}}var De=new Map;function Fe(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function Ie(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function ut(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function B(e){let t=De.get(e);if(t)return t;let n=Ie(e),r=ut(n),i="",a=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(i+=Fe(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(i+="(?:.*/)?",c+=2):(i+=".*",c+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=Fe(p)}let s=new RegExp(`^${i}$`);return De.set(e,s),s}function xe(e){let t=Ie(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function z(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(s=>B(s).test(n))){for(let s of a.patterns??[])if(B(s).test(n)){let c=xe(s);c>i&&(i=c,r=a.name)}}return r}function je(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function yt(e){let t=new Set;for(let n of e??[]){let i=Ie(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let s=i[a];if((s==="**"||s==="*")&&a>0){let c=i[a-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function gt(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return yt(r?.patterns)}function v(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,s=r?.toPath;if(!a||!s)continue;let c=gt(i,t,r?.layers);if(c.length===0)continue;let p=je(a,c),y=je(s,c);if(!p||!y)continue;if(p!==y)return i;continue}if(t!==n)return i}}function D(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function He(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Ve(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function Ue(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function Se(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ke(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):Se(t,r)}catch{a=void 0}return!!a?.declarations?.some(s=>s.getSourceFile().fileName===n.fileName)}function F(e,t){let n,r=[],i=(s,c,p,y=!1)=>r.push({specifier:p,kind:c,line:He(t,s),typeOnly:y,unresolved:p===void 0,node:s}),a=s=>{if(e.isImportDeclaration(s))i(s,"import",D(e,s.moduleSpecifier),Ve(e,s));else if(e.isExportDeclaration(s)&&s.moduleSpecifier)i(s,"export",D(e,s.moduleSpecifier),Ve(e,s));else if(e.isImportEqualsDeclaration(s)&&e.isExternalModuleReference(s.moduleReference))i(s,"require",D(e,s.moduleReference.expression));else if(e.isCallExpression(s)){let c=s.expression.kind===e.SyntaxKind.ImportKeyword,y=e.isIdentifier(s.expression)&&s.expression.text==="require"&&!ke(e,n??(n=Ue(e,t)),t,s.expression);(c||y)&&i(s,y?"require":"dynamic-import",D(e,s.arguments[0]))}e.forEachChild(s,a)};return a(t),r}function mt(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=D(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function ht(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function Ge(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function j(e,t,n){if(n.length===0)return[];let r=new Set(n),i=Ue(e,t),a=new Map,s=new Set;for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations)e.isIdentifier(o.name)&&s.add(o.name.text);let c=l=>{let o=mt(e,l);if(!o)return;let g=Se(i,o.root),f=g?a.get(g):void 0;return f?[...f,...o.segments.slice(1)]:ke(e,i,t,o.root)||s.has(o.root.text)?void 0:o.segments};for(let l of t.statements)if(e.isVariableStatement(l))for(let o of l.declarationList.declarations){if(!o.initializer||!e.isIdentifier(o.name))continue;let g=c(o.initializer),f=Se(i,o.name);!g||!f||a.set(f,g)}let p=[],y=new Set,u=(l,o)=>{let g=He(t,o),f=`${l}:${o.getStart(t)}`;y.has(f)||(y.add(f),p.push({name:l,line:g,node:o}))},m=l=>{let o=l.parent&&(e.isPropertyAccessExpression(l.parent)||e.isElementAccessExpression(l.parent))&&l.parent.expression===l;if((e.isPropertyAccessExpression(l)||e.isElementAccessExpression(l))&&!o){let g=c(l),f=g?Ge(r,g):void 0;f&&u(f,l)}else e.isIdentifier(l)&&r.has(l.text)&&ht(e,l)&&!ke(e,i,t,l)&&u(l.text,l);if(e.isVariableDeclaration(l)&&e.isObjectBindingPattern(l.name)&&l.initializer){let g=c(l.initializer);if(g)for(let f of l.name.elements){if(!e.isIdentifier(f.name))continue;let I=f.propertyName?D(e,f.propertyName)??f.propertyName.text:f.name.text,x=Ge(r,[...g,I]);x&&u(x,l.initializer)}}e.forEachChild(l,m)};return m(t),p}function Ee(e,t){let n=[];for(let r of F(e,t)){if(r.typeOnly||!r.specifier)continue;let i=U(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of j(e,t,be)){let i=X(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var we={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function _(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Le(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&_(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:we.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:we.PUBLISH_MISSING_SOURCE}),t}function b(e,t,n){return{ruleId:e,code:e,message:t,...n}}function V(e,t){return e.slice(0,t).split(`
|
|
2
2
|
`).length}function At(e){let t=[],n=/['"`]([A-Za-z][A-Za-z0-9_.]*)['"`]/g,r;for(;(r=n.exec(e))!==null;)t.push({value:r[1],index:r.index});return t}function bt(e){let t=[],n=[{kind:"import",re:/\bimport\s+(?:type\s+)?(?:[^'"]*?\s+from\s*)?['"]([^'"]+)['"]/g},{kind:"export",re:/\bexport\s+(?:type\s+)?[^'"]*?\s+from\s*['"]([^'"]+)['"]/g},{kind:"dynamic-import",re:/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g},{kind:"require",re:/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g}];for(let r of n){let i;for(;(i=r.re.exec(e))!==null;){let a=i.index+i[0].indexOf(i[1]),s=i[0],c=r.kind==="import"&&/\bimport\s+type\b/.test(s)||r.kind==="export"&&/\bexport\s+type\b/.test(s);t.push({value:i[1],index:a,kind:r.kind,typeOnly:c})}}return t.sort((r,i)=>r.index-i.index)}function Ct(e,t){let n=e.createSourceFile("generated.ts",t,e.ScriptTarget.Latest,!0),r=[],i=a=>{e.isStringLiteralLike(a)&&r.push({value:a.text,index:a.getStart(n)}),e.forEachChild(a,i)};return i(n),r}function It(e){let t=e.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);return["adapter","adapters","infra","infrastructure","persistence","repository","repositories","integration","database","db"].some(n=>t.includes(n))}function xt(e){let t=e.toLowerCase();return["sequelize","prisma","typeorm","mongoose","knex"].some(n=>t===n||t.startsWith(`${n}/`))}function St(e){let t=e.toLowerCase();return["adapter","infra","persistence","repository","repositories","integration","database"].some(n=>t.includes(n))}function q(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function kt(e,t){if(t&&(e.isIdentifier(t)||e.isStringLiteralLike(t)))return t.text}function Be(e,t,n){if(!(!t||!e.isObjectLiteralExpression(t)))return t.properties.find(r=>!e.isPropertyAssignment(r)&&!e.isShorthandPropertyAssignment(r)?!1:kt(e,r.name)===n)}function K(e,t,n){return Be(e,t,n)!==void 0}function Y(e,t,n){let r=Be(e,t,n);return r&&e.isPropertyAssignment(r)?r.initializer:void 0}function Et(e,t){let n=Y(e,t,"metadata");return K(e,n,"source")}function ze(e,t){return t?e.isIdentifier(t)?/^[A-Z]/.test(t.text):e.isPropertyAccessExpression(t)?ze(e,t.name):!1:!1}function wt(e,t){if(!e.isCallExpression(t))return!1;let n=t.expression;return e.isPropertyAccessExpression(n)?n.name.text==="publish":e.isIdentifier(n)&&n.text==="publish"}function Lt(e,t){if(!e.isCallExpression(t))return!1;let n=t.arguments[0],r=q(e,n);return r!==void 0&&_(r)||K(e,n,"intent")||ze(e,n)}function Pt(e,t){if(!e.isCallExpression(t))return!1;let[n,r,i]=t.arguments;return Et(e,n)||K(e,r,"source")||K(e,i,"source")}function Ot(e,t){if(!e.isCallExpression(t))return;let[n,r,i]=t.arguments,a=Y(e,n,"metadata");return q(e,Y(e,a,"source"))??q(e,Y(e,r,"source"))??q(e,Y(e,i,"source"))}function Rt(e,t,n,r){let i=e.createSourceFile("generated.ts",t,e.ScriptTarget.Latest,!0),a=n,s=a?.filePath,c=a?.layer,p=[],y=m=>i.getLineAndCharacterOfPosition(m.getStart(i)).line+1,u=m=>{if(wt(e,m)){let l=m.arguments[0],o=q(e,l);for(let f of Le({publishCall:!0,rawIntentName:o,objectHasIntent:K(e,l,"intent"),arkPublishCandidate:Lt(e,m),hasSource:Pt(e,m)}))p.push(b(f.ruleId,f.message,{line:y(m),filePath:s}));let g=Ot(e,m);if(r&&c&&g&&_(g)){let f=r.resolveLayer(g);f&&f!==c&&p.push(b("PUBLISH_SOURCE_LAYER_MISMATCH",`Publish source "${g}" resolves to ${f}, but the target file is classified as ${c}.`,{line:y(m),filePath:s,target:g,fromLayer:c,toLayer:f}))}}e.forEachChild(m,u)};return u(i),p}function Ye(e={}){let t=new Set((e.intents||[]).map(a=>typeof a=="string"?a:a.name)),n=e.forbiddenPatterns||[],r=new Set(e.infrastructureLayers??[]),i=e.enforceIntentAllowlist??t.size>0;return{validate(a,s){let c=[],p=s,y=p?.filePath,u=p?.layer,m=e.typescript,l=m?m.createSourceFile(y??"generated.ts",a,m.ScriptTarget.Latest,!0):void 0,o=l?F(e.typescript,l):void 0,g=o?o.filter(d=>d.specifier!==void 0).map(d=>({value:d.specifier,index:d.node.getStart(l),kind:d.kind,typeOnly:d.typeOnly})):bt(a),f=e.typescript?Ct(e.typescript,a):At(a);if(e.typescript&&!e.allowNonLiteralDynamicImport?.(y))for(let d of o?.filter(({unresolved:h})=>h)??[]){let h=d.kind==="require";c.push(b(h?"DYNAMIC_REQUIRE_NOT_ALLOWLISTED":"DYNAMIC_IMPORT_NOT_ALLOWLISTED",`Non-literal ${h?"require call":"dynamic import"} cannot be resolved statically; add the reviewed file to dynamicImportAllowlist.`,{line:d.line,filePath:y}))}let I=u!==void 0&&(r.has(u)||St(u)),x=u!==void 0?` If "${u}" is an infrastructure layer, mark it in ark.config.json with "mayImportInfrastructure": true (or name it with an infra token like Adapters/Persistence/Repository).`:"";for(let d of n)if(d instanceof RegExp){d.lastIndex=0;let h=d.exec(a);d.lastIndex=0,h&&c.push(b("FORBIDDEN_PATTERN",`Forbidden pattern matched: ${d}`,{line:h.index===void 0?void 0:V(a,h.index),filePath:y,suggestion:"Remove infrastructure imports from domain/application layers."+x}))}else a.includes(d)&&c.push(b("FORBIDDEN_SUBSTRING",`Forbidden substring: ${d}`,{line:V(a,a.indexOf(d)),filePath:y}));for(let d of g){let h=e.resolveImportTarget?.(d.value,y)??(e.resolveImportLayer?{layer:e.resolveImportLayer(d.value,y)}:void 0),A=typeof y=="string"?e.resolveImportTarget?.(y)??(e.resolveImportLayer?{layer:u,relPath:void 0}:void 0):void 0,T=h?.layer;if(T&&u){let Q=v(e.architectureProfile?.rules,u,T,{fromPath:A?.relPath,toPath:h?.relPath,layers:e.architectureLayers});if(Q){if(d.typeOnly&&!Q.peerIsolation)continue;let ye=!!Q.peerIsolation;c.push(b("LAYER_IMPORT_VIOLATION",Q.message??(ye?`Layer "${u}" must not import across slices into "${T}".`:`Layer "${u}" must not import "${T}".`),{line:V(a,d.index),source:d.value,target:d.value,filePath:y,fromLayer:u,toLayer:T,suggestion:ye?"Extract shared code to a shared layer, or coordinate slices via events/ports \u2014 do not import across feature/context slices.":"Depend on a port/interface owned by an inner layer instead, or move this code to a layer allowed to make this import.",details:{importKind:d.kind,peerIsolation:ye,...d.typeOnly?{typeOnly:!0}:{}}}));continue}if(T!==u)continue}I||d.typeOnly||!It(d.value)&&!xt(d.value)||c.push(b("FORBIDDEN_IMPORT",`Forbidden ${d.kind} target: "${d.value}".`,{line:V(a,d.index),source:d.value,target:d.value,filePath:y,suggestion:"Route infrastructure access through an allowed adapter or port boundary."+x,details:{importKind:d.kind}}))}if(e.policies)for(let d of e.policies){let h=d.check({source:a,context:s});if(h!==!0)if(Array.isArray(h))for(let A of h)c.push(b("POLICY_VIOLATION",A.message,{filePath:y,suggestion:`Fix violation of policy "${d.name}".`}));else h===!1?c.push(b("POLICY_VIOLATION",`Policy ${d.name} failed on generated code`)):c.push(b("POLICY_VIOLATION",h.message))}if(i&&t.size>0)for(let d of f)_(d.value)&&!t.has(d.value)&&c.push(b("UNKNOWN_INTENT",`Unknown intent reference: "${d.value}"`,{line:V(a,d.index),filePath:y,target:d.value,suggestion:`Register intent "${d.value}" via defineIntent() or remove the reference.`}));if(e.architectureProfile&&u)for(let d of f){if(!_(d.value))continue;let h=e.architectureProfile.resolveLayer(d.value);if(!h)continue;let A=v(e.architectureProfile.rules,u,h);A&&c.push(b("LAYER_REFERENCE_VIOLATION",A.message??`Layer "${u}" must not reference "${h}" through "${d.value}".`,{line:V(a,d.index),filePath:y,target:d.value,fromLayer:u,toLayer:h,suggestion:"Route the dependency through an allowed intent, port, or event.",details:{rule:A}}))}if(e.extensions)for(let d of e.extensions)try{let h=d.analyze(a,s);c.push(...h)}catch(h){c.push(b("EXTENSION_ERROR",`Extension "${d.name}" failed: ${h instanceof Error?h.message:String(h)}`))}if(e.typescript&&l&&u&&e.forbiddenGlobals?.[u]?.length)try{c.push(...j(e.typescript,l,e.forbiddenGlobals[u]).map(d=>b("FORBIDDEN_GLOBAL",`${u} must not use the ambient global "${d.name}".`,{line:d.line,filePath:y,target:d.name,fromLayer:u,suggestion:"Inject the capability through a port (e.g. a Clock, IdGenerator, or HttpPort) instead of reaching for the ambient global."})))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}if(e.typescript&&l&&u&&e.capabilityWalls?.[u]?.length)try{let d=new Set(e.capabilityWalls[u]),h=e.forbiddenGlobals?.[u]??[];for(let A of Ee(e.typescript,l))d.has(A.capability)&&(A.source==="ambient-global"&&Ce(A.symbol,h)||c.push(b("CAPABILITY_VIOLATION",A.source==="import-based"?`${u} denies the ${A.capability} capability; found import of "${A.symbol}".`:`${u} denies the ${A.capability} capability; found ambient "${A.symbol}".`,{line:A.line,filePath:y,target:A.symbol,capability:A.capability,fromLayer:u,suggestion:"Define a small port (ClockPort, HttpPort, StoragePort) and bind the implementation in an adapter layer."})))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}if(e.typescript)try{c.push(...Rt(e.typescript,a,s,e.architectureProfile))}catch(d){c.push(b("AST_ANALYZER_ERROR",`TypeScript AST analyzer failed: ${d instanceof Error?d.message:String(d)}`))}return{valid:c.length===0,violations:c}}}}var $t="1.0",re="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",qe=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],vt=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function _t(){let e=[];for(let t of qe)for(let n of qe)t===n||vt.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var ie=_t();var O={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},Oe={$schema:"https://json-schema.org/draft/2020-12/schema",$id:re,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:re,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...O,minItems:1,default:["src"]},exclude:{...O,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:ie,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...O,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...O,minItems:1},exclude:O,intentPrefixes:O,description:{type:"string",minLength:1},forbiddenGlobals:O,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...O,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},H=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
|
|
3
3
|
${n.map(r=>`- ${r.path}: ${r.message}`).join(`
|
|
4
4
|
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function Ke(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function Pe(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function G(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Nt(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function ne(e,t,n,r,i){if(t.$ref){let a=Nt(t.$ref,r);if(!a){i.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}ne(e,a,n,r,i);return}if(t.const!==void 0&&!Object.is(e,t.const)){i.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(a=>Object.is(a,e))){i.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!Ke(e)){i.push({path:n,message:`must be an object; received ${G(e)}`});return}let a=t.properties??{};for(let s of t.required??[])e[s]===void 0&&i.push({path:Pe(n,s),message:"is required"});if(t.additionalProperties===!1)for(let s of Object.keys(e))s in a||i.push({path:Pe(n,s),message:"unknown field"});for(let[s,c]of Object.entries(a))e[s]!==void 0&&ne(e[s],c,Pe(n,s),r,i);return}if(t.type==="array"){if(!Array.isArray(e)){i.push({path:n,message:`must be an array; received ${G(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&i.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let a=e.map(s=>JSON.stringify(s));new Set(a).size!==a.length&&i.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((a,s)=>ne(a,t.items,`${n}[${s}]`,r,i));return}if(t.type==="string"){if(typeof e!="string"){i.push({path:n,message:`must be a string; received ${G(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&i.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&i.push({path:n,message:`must be a boolean; received ${G(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){i.push({path:n,message:`must be an integer; received ${G(e)}`});return}t.minimum!==void 0&&e<t.minimum&&i.push({path:n,message:`must be at least ${t.minimum}`})}}function Mt(e){return{...e,$schema:e.$schema===void 0?re:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?ie.map(t=>({...t})):e.rules}}function Tt(e,t="ark.config.json"){if(!Ke(e))throw new H(t,[{path:"$",message:`must be an object; received ${G(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new H(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:Mt(e),migratedFrom:n}}function ae(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=Tt(e,t),i=[];if(ne(n,Oe,"$",Oe,i),i.length>0)throw new H(t,i);return{config:n,migratedFrom:r}}function Re(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new H(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return ae(n,t)}function We(e){let t={$schema:typeof e.$schema=="string"&&e.$schema.length>0?e.$schema:re,schemaVersion:"1.0"};for(let[n,r]of Object.entries(e))n!=="$schema"&&n!=="schemaVersion"&&(t[n]=r);return t}function Dt(e){return e.endsWith(".")?e:`${e}.`}function Ft(e,t){let n=e.prefixes.length?Math.max(...e.prefixes.map(i=>i.length)):0;return(t.prefixes.length?Math.max(...t.prefixes.map(i=>i.length)):0)-n}function se(e){let t=e.layers.map(i=>({...i,prefixes:i.prefixes.map(Dt)})),n=[...t].sort(Ft),r=[...e.rules??[]];return{name:e.name,layers:t,rules:r,resolveLayer(i){return t.find(a=>a.match?.(i))?.name??n.find(a=>a.prefixes.some(s=>i.startsWith(s)))?.name}}}function Je(e,t={}){return se({name:t.name??e.name??"ark.config.json",layers:e.layers.map((n,r)=>({name:n.name,prefixes:n.intentPrefixes??[],description:n.description,order:r+1})),rules:e.rules??[]})}var jt=[{name:"DomainModel",prefixes:["Domain"],description:"Rich domain model, business rules, and domain events.",order:1},{name:"ApplicationOrchestration",prefixes:["Application"],description:"Use cases and command orchestration.",order:2},{name:"PersistenceAdapters",prefixes:["Adapter.Persistence","Adapter.Repository"],description:"Database, repository, and storage adapters.",order:3},{name:"IntegrationAdapters",prefixes:["Adapter.Integration","Adapter.External"],description:"External systems, APIs, and integration adapters.",order:4},{name:"WorkflowSagaEngine",prefixes:["Workflow"],description:"Sagas, workflows, and long-running processes.",order:5},{name:"BackgroundJobsScheduling",prefixes:["Job"],description:"Background jobs, scheduled work, and async processors.",order:6},{name:"PresentationAdapters",prefixes:["Presentation","Adapter.Presentation","Adapter.Api"],description:"API, UI, controller, and presentation adapters.",order:7},{name:"ReportingReadModels",prefixes:["Reporting"],description:"Read models, projections, and reporting surfaces.",order:8},{name:"ExtensibilityMetadata",prefixes:["Metadata"],description:"Metadata, extensions, and schema contracts.",order:9},{name:"SecurityAuditObservability",prefixes:["Security","Audit","Observability"],description:"Security, audit, and observability concerns.",order:10},{name:"Kernel",prefixes:["Kernel"],description:"Ark-owned governance and kernel signals.",order:11}],oe=se({name:"Ark 11-layer Hexagonal Event-Driven Profile",layers:jt,rules:ie.map(e=>({...e}))}),Vt={DomainModel:["domain"],ApplicationOrchestration:["application","app"],PersistenceAdapters:["adapters/persistence","adapters/repository","repositories","infra/persistence"],IntegrationAdapters:["adapters/integration","adapters/external","integrations"],WorkflowSagaEngine:["workflows","sagas"],BackgroundJobsScheduling:["jobs","schedules"],PresentationAdapters:["presentation","adapters/presentation","adapters/api"],ReportingReadModels:["reporting","read-models","projections"],ExtensibilityMetadata:["metadata","extensions"],SecurityAuditObservability:["security","audit","observability"],Kernel:["kernel"]};function Ze(e={}){let t=e.rootDir??"src",n=e.optionalLayers??!0,r=t==="."?"":`${t}/`;return We({include:e.include??[t],layers:oe.layers.map(i=>({name:i.name,patterns:(Vt[i.name]??[i.name]).map(a=>`${r}${a}/**`),intentPrefixes:i.prefixes,optional:n})),rules:[...oe.rules]})}var $e="1.0";function R(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function L(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(L).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(n=>`${JSON.stringify(n)}:${L(t[n])}`).join(",")}}`}function E(e){return`${e.from}->${e.to}`}function W(e,t,n,r="dependency"){return{id:`${e}:${r}:${E(t)}`,classification:e,subject:"dependency",from:t.from,to:t.to,message:n,...e==="missing"?{nextAction:`Add the planned dependency ${E(t)} to the candidate, then preflight again.`}:e==="contradictory"?{nextAction:`Replace the reverse dependency with ${E(t)}, then preflight again.`}:e==="unplanned"?{nextAction:`Remove the unplanned dependency ${E(t)} from the candidate, then preflight again.`}:{}}}function ce(e){let t=[],n=new Map(e.changeMap.map.files.map(l=>[l.path,l])),r=new Map(e.changes.map(l=>[l.path,l])),i=new Map(e.changeMap.map.dependencies.map(l=>[E(l),l])),a=new Map(e.baseDependencies.map(l=>[E(l),l])),s=new Map(e.candidateDependencies.map(l=>[E(l),l]));for(let l of[...n.values()].sort((o,g)=>o.path.localeCompare(g.path))){let o=r.get(l.path);o?o.operation!==l.operation?t.push({id:`contradictory:file:${l.path}`,classification:"contradictory",subject:"file",path:l.path,expectedOperation:l.operation,actualOperation:o.operation,message:`${l.path} was planned as ${l.operation} but the actual operation is ${o.operation}.`,nextAction:`Change ${l.path} to the planned ${l.operation} operation, then preflight again.`}):t.push({id:`satisfied:file:${l.path}`,classification:"satisfied",subject:"file",path:l.path,expectedOperation:l.operation,actualOperation:o.operation,message:`${l.path} matches the planned ${l.operation} operation.`}):t.push({id:`missing:file:${l.path}`,classification:"missing",subject:"file",path:l.path,expectedOperation:l.operation,message:`${l.path} was planned as ${l.operation} but is absent from the actual change.`,nextAction:`${l.operation[0].toUpperCase()}${l.operation.slice(1)} ${l.path} in the complete change set, then preflight again.`})}for(let l of[...r.values()].sort((o,g)=>o.path.localeCompare(g.path)))n.has(l.path)||t.push({id:`unplanned:file:${l.path}`,classification:"unplanned",subject:"file",path:l.path,actualOperation:l.operation,message:`${l.path} has an unplanned ${l.operation} operation.`,nextAction:`Remove ${l.path} from the change set, then preflight again.`});let c=new Set;for(let l of[...i.values()].sort((o,g)=>E(o).localeCompare(E(g)))){if(s.has(E(l))){t.push(W("satisfied",l,`${l.from} -> ${l.to} exists in the candidate architecture.`));continue}let o={from:l.to,to:l.from};s.has(E(o))?(c.add(E(o)),t.push(W("contradictory",l,`${l.from} -> ${l.to} was planned, but the candidate contains the reverse edge.`))):t.push(W("missing",l,`${l.from} -> ${l.to} is absent from the candidate architecture.`))}let p=new Set([...n.keys(),...r.keys()]);for(let[l,o]of[...s].sort(([g],[f])=>g.localeCompare(f)))a.has(l)||i.has(l)||c.has(l)||!p.has(o.from)&&!p.has(o.to)||t.push({...W("unplanned",o,`${o.from} -> ${o.to} was added without a matching planned dependency.`,"dependency-added"),actualOperation:"added"});let y=new Set(e.changeMap.map.files.filter(l=>l.operation==="delete").map(l=>l.path));for(let[l,o]of[...a].sort(([g],[f])=>g.localeCompare(f)))s.has(l)||y.has(o.from)||y.has(o.to)||!p.has(o.from)&&!p.has(o.to)||t.push({...W("unplanned",o,`${o.from} -> ${o.to} was removed without a planned file deletion.`,"dependency-removed"),actualOperation:"removed"});let u={satisfied:0,missing:1,contradictory:2,unplanned:3};t.sort((l,o)=>u[l.classification]-u[o.classification]||(l.subject===o.subject?0:l.subject==="file"?-1:1)||l.id.localeCompare(o.id));let m={satisfied:t.filter(l=>l.classification==="satisfied").length,missing:t.filter(l=>l.classification==="missing").length,contradictory:t.filter(l=>l.classification==="contradictory").length,unplanned:t.filter(l=>l.classification==="unplanned").length};return{schemaVersion:"1.0",readOnly:!0,changeMapHash:e.changeMap.hash,structurallyConverged:m.missing===0&&m.contradictory===0&&m.unplanned===0,behavioralCompletion:"not-evaluated",summary:m,findings:t}}var ve="1.0";function C(e,t){e.push({id:`${t.classification}:${t.path}:${t.kind}`,path:t.path,classification:t.classification,message:t.message,...t.classification==="weakening"||t.classification==="judgment-required"?{nextAction:`Restore the previous protection at ${t.path}, then run ArkGate again.`}:{},...t.before===void 0?{}:{before:t.before},...t.after===void 0?{}:{after:t.after}})}function w(e){return[...new Set(e??[])].sort()}function N(e,t,n,r,i){let a=w(n),s=w(r),c=new Set(a),p=new Set(s),y=s.filter(m=>!c.has(m)),u=a.filter(m=>!p.has(m));y.length===0&&u.length===0||(y.length>0&&C(e,{kind:"added",path:t,classification:i.added,message:i.addedMessage,before:a,after:s}),u.length>0&&C(e,{kind:"removed",path:t,classification:i.removed,message:i.removedMessage,before:a,after:s}))}function le(e,t,n,r,i,a,s){if(n===r)return;C(e,{kind:r?"enabled":"disabled",path:t,classification:r?i:i==="strengthening"?"weakening":"strengthening",message:r?a:s,before:n,after:r})}function pe(e,t){let n=new Map,r=new Set;for(let i of e){let a=t(i);n.has(a)?r.add(a):n.set(a,i)}return{values:n,duplicates:[...r].sort()}}function Gt(e,t,n){let r=pe(t,a=>a.name),i=pe(n,a=>a.name);(r.duplicates.length>0||i.duplicates.length>0)&&C(e,{kind:"duplicate-layer",path:"$.layers",classification:"judgment-required",message:"Duplicate layer names make policy ownership ambiguous.",before:r.duplicates,after:i.duplicates});for(let a of[...new Set([...r.values.keys(),...i.values.keys()])].sort()){let s=r.values.get(a),c=i.values.get(a),p=`$.layers[${a}]`;if(!s&&c){C(e,{kind:"layer-added",path:p,classification:"judgment-required",message:"A layer was added; verify overlap, ownership, and rule coverage.",after:c});continue}if(s&&!c){C(e,{kind:"layer-removed",path:p,classification:"weakening",message:"Removing a layer can leave its source paths ungoverned.",before:s});continue}if(!s||!c)continue;N(e,`${p}.patterns`,s.patterns,c.patterns,{added:"strengthening",removed:"weakening",addedMessage:"Additional paths are governed by this layer.",removedMessage:"Paths were removed from this layer and may become ungoverned."}),N(e,`${p}.exclude`,s.exclude,c.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional paths are excluded from this layer.",removedMessage:"Fewer paths are excluded from this layer."});let y=te(s),u=te(c);N(e,`${p}.forbiddenGlobals`,y.rawGlobals,u.rawGlobals,{added:"strengthening",removed:"weakening",addedMessage:"Additional forbidden globals are enforced in this layer.",removedMessage:"A forbidden-global protection was removed from this layer."}),N(e,`${p}.capabilities`,y.atoms,u.atoms,{added:"strengthening",removed:"weakening",addedMessage:"Additional ambient/import protection is enforced in this layer (coverage atoms).",removedMessage:"An ambient/import protection was lost from this layer (coverage atoms)."}),w(s.intentPrefixes).join("\0")!==w(c.intentPrefixes).join("\0")&&C(e,{kind:"intent-prefixes-changed",path:`${p}.intentPrefixes`,classification:"judgment-required",message:"Intent ownership changed and must be reviewed against publishers and consumers.",before:w(s.intentPrefixes),after:w(c.intentPrefixes)}),le(e,`${p}.mayImportInfrastructure`,s.mayImportInfrastructure===!0,c.mayImportInfrastructure===!0,"weakening","The layer may now import infrastructure directly.","Direct infrastructure imports are no longer allowed for this layer."),le(e,`${p}.optional`,s.optional===!0,c.optional===!0,"weakening","The layer is now optional and can be absent without a strict warning.","The layer is now required when its contract is active.")}}function Ht(e,t,n){let r=s=>`${s.from}->${s.to}`,i=pe(t,r),a=pe(n,r);(i.duplicates.length>0||a.duplicates.length>0)&&C(e,{kind:"duplicate-rule",path:"$.rules",classification:"judgment-required",message:"Duplicate rule edges make the effective verdict order-dependent.",before:i.duplicates,after:a.duplicates});for(let s of[...new Set([...i.values.keys(),...a.values.keys()])].sort()){let c=i.values.get(s),p=a.values.get(s),y=`$.rules[${s}]`;if(!c&&p){p.allowed===!1&&C(e,{kind:"deny-added",path:y,classification:"strengthening",message:"A denied dependency edge was added.",after:p});continue}if(c&&!p){c.allowed===!1&&C(e,{kind:"deny-removed",path:y,classification:"weakening",message:"A denied dependency edge was removed.",before:c});continue}if(!c||!p)continue;c.allowed!==p.allowed&&C(e,{kind:p.allowed?"deny-disabled":"deny-enabled",path:`${y}.allowed`,classification:p.allowed?"weakening":"strengthening",message:p.allowed?"A previously denied dependency edge is now allowed.":"A dependency edge is now denied.",before:c.allowed,after:p.allowed});let u=c.peerIsolation===!0,m=p.peerIsolation===!0;if(u!==m){let l=c.from===c.to&&p.from===p.to;C(e,{kind:m?"peer-isolation-enabled":"peer-isolation-disabled",path:`${y}.peerIsolation`,classification:l?m?"strengthening":"weakening":"judgment-required",message:l?m?"Cross-slice dependencies inside this layer are now denied.":"Cross-slice dependencies inside this layer are no longer denied.":"Changing peer isolation on a cross-layer edge changes the denial scope.",before:u,after:m})}w(c.sliceFolders).join("\0")!==w(p.sliceFolders).join("\0")&&C(e,{kind:"slice-folders-changed",path:`${y}.sliceFolders`,classification:"judgment-required",message:"Slice ownership folders changed and can reclassify existing dependencies.",before:w(c.sliceFolders),after:w(p.sliceFolders)})}}function Ut(e,t,n){let r=t.safety??{},i=n.safety??{};for(let a of["maxTsSuppressions","maxAnyCasts"]){let s=r[a]??0,c=i[a]??0;s!==c&&C(e,{kind:c>s?"threshold-raised":"threshold-lowered",path:`$.safety.${a}`,classification:c>s?"weakening":"strengthening",message:c>s?"The safety threshold allows more violations.":"The safety threshold allows fewer violations.",before:s,after:c})}for(let a of["allowInMemory","allowDisabledPeerIsolation"])le(e,`$.safety.${a}`,r[a]===!0,i[a]===!0,"weakening","A safety exception was enabled.","A safety exception was disabled.")}function Bt(e){return e.some(t=>t.classification==="weakening")?"weakening":e.some(t=>t.classification==="judgment-required")?"judgment-required":e.some(t=>t.classification==="strengthening")?"strengthening":"neutral"}function _e(e,t){let n=[];N(n,"$.include",e.include,t.include,{added:"strengthening",removed:"weakening",addedMessage:"Additional project roots are governed.",removedMessage:"Project roots were removed from governance."}),N(n,"$.exclude",e.exclude,t.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional project paths are excluded from governance.",removedMessage:"Fewer project paths are excluded from governance."}),N(n,"$.dynamicImportAllowlist",e.dynamicImportAllowlist,t.dynamicImportAllowlist,{added:"weakening",removed:"strengthening",addedMessage:"Additional files may use non-literal dynamic imports.",removedMessage:"Fewer files may use non-literal dynamic imports."}),le(n,"$.excludeGenerated",e.excludeGenerated!==!1,t.excludeGenerated!==!1,"weakening","Generated source is now excluded from governance.","Generated source is now governed.");let r={off:0,soft:1,"framework-soft":1,strict:2},i=e.cyclePolicy??"strict",a=t.cyclePolicy??"strict";if(i!==a){let s=r[a]===r[i]?"judgment-required":r[a]>r[i]?"strengthening":"weakening";C(n,{kind:"cycle-policy-changed",path:"$.cyclePolicy",classification:s,message:"The cycle enforcement level changed.",before:i,after:a})}return(e.frameworkOverlay??null)!==(t.frameworkOverlay??null)&&C(n,{kind:"framework-overlay-changed",path:"$.frameworkOverlay",classification:"judgment-required",message:"The framework overlay changed and may alter effective layer matching.",before:e.frameworkOverlay??null,after:t.frameworkOverlay??null}),Gt(n,e.layers,t.layers),Ht(n,e.rules,t.rules),Ut(n,e,t),n.sort((s,c)=>s.path.localeCompare(c.path)||s.id.localeCompare(c.id)),{schemaVersion:ve,classification:Bt(n),findings:n}}function Ne(e,t){if(!e||e.schemaVersion!==ve||typeof e.basePolicyHash!="string"||typeof e.candidatePolicyHash!="string"||typeof e.reason!="string"||!Array.isArray(e.findingIds)||e.findingIds.some(i=>typeof i!="string")||e.reason.trim().length===0||e.basePolicyHash!==t.basePolicyHash||e.candidatePolicyHash!==t.candidatePolicyHash)return!1;let n=w(e.findingIds),r=w(t.findingIds);return n.length===r.length&&n.every((i,a)=>i===r[a])}function M(e){let t=[];for(let n of e.replace(/\\/g,"/").split("/"))!n||n==="."||(n===".."&&t.length>0&&t.at(-1)!==".."?t.pop():t.push(n));return t.join("/")}function Qe(e){return e!==void 0&&/[A-Za-z0-9_$]/.test(e)}function $(e,t){for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t}function J(e,t){let n=e[t];if(n!=="'"&&n!=='"')return;let r=t,i="";for(t+=1;t<e.length;t+=1){let a=e[t];if(a===n)return{value:i,offset:r,excerpt:e.slice(r,t+1)};a==="\\"&&t+1<e.length?(i+=e[t+1],t+=1):i+=a}}function P(e,t,n){return e.startsWith(t,n)&&!Qe(e[n-1])&&!Qe(e[n+t.length])}function zt(e,t){if(t=$(e,t+6),e[t]==="(")return J(e,$(e,t+1));let n=!1;if(P(e,"type",t)){let i=$(e,t+4);e[i]!==","&&!P(e,"from",i)&&(n=!0)}let r=Xe(e,t,!0);return r&&n?{...r,typeOnly:!0}:r}function Yt(e,t){t=t+6;let n=$(e,t),r=!1;if(P(e,"type",n)){let a=$(e,n+4);(e[a]==="{"||e[a]==="*")&&(r=!0)}let i=Xe(e,t,!1);return i&&r?{...i,typeOnly:!0}:i}function Xe(e,t,n){for(;t<e.length;t+=1){if(e[t]===";")return;if(P(e,"from",t))return J(e,$(e,t+4));if(n&&(e[t]==="'"||e[t]==='"'))return J(e,t);if(t>0&&(P(e,"import",t)||P(e,"export",t)))return}}function qt(e,t){for(t+=1;t<e.length;t+=1){let n=e[t];if(n==="\\")t+=1;else if(n==="`")return t}return e.length}function Kt(e,t){let n=t-1;for(;n>=0&&/\s/.test(e[n]);)n-=1;if(e[n]===".")return;let r=$(e,t+7);if(e[r]!=="(")return;r=$(e,r+1);let i=J(e,r);return i?{...i,requireCall:!0}:void 0}function Wt(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="/"&&e[n+1]==="/"){if(n=e.indexOf(`
|
package/docs/agent-guide.md
CHANGED
|
@@ -134,7 +134,9 @@ applied acks only, and a malformed sidecar or edge string suppresses nothing. X0
|
|
|
134
134
|
lifecycle: an optional `reviewBy` (`YYYY-MM-DD`) marks when the exception must be re-reviewed;
|
|
135
135
|
past that date the ack stops applying and the smell returns annotated (`(ack expired …)`).
|
|
136
136
|
Undated acks keep applying but are counted in `contractHealth.ackLifecycle.undated` — give
|
|
137
|
-
migration acks a date so they cannot fossilize.
|
|
137
|
+
migration acks a date so they cannot fossilize. X05 — acks matching no detected edge are listed
|
|
138
|
+
as `ackLifecycle.stale`: fix the edge string or delete the entry. X06 — the family-infra
|
|
139
|
+
carve-out also matches mid-name families (`HoursPersistenceAdapters -> PersistenceInfrastructure`).
|
|
138
140
|
|
|
139
141
|
**Effect capabilities (U03, evidence-only):** the analysis IR reports typed capability uses for
|
|
140
142
|
seven closed ids (`network`, `filesystem`, `clock`, `randomness`, `environment`, `process`,
|
|
@@ -154,6 +156,14 @@ in `pure: true` layers only. Acknowledge deliberate registries/caches in
|
|
|
154
156
|
`.ark/ambient-state-acks.json` (`{ acks: [{ file, name, reason }] }`) or move the state behind a
|
|
155
157
|
port. Advisory only — never blocks, never feeds `designFitness`; no strict mode exists.
|
|
156
158
|
|
|
159
|
+
**Physical cohesion + reshape pilot (X04, advisory):** `doctor.physicalCohesion` reports concept
|
|
160
|
+
clusters per anchor directory (concentration, not volume — dispersed hooks never fire) with
|
|
161
|
+
fixed corpus-calibrated thresholds; anchors under `app/`/`pages/` are `fixedByConvention` and
|
|
162
|
+
never move. `reshapePilot.nextPilot` is a **proposed** one-at-a-time card (`moveSample`,
|
|
163
|
+
`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`): run it only via `/ark-loop` through the
|
|
164
|
+
write gate + atomic preflight; merges are `/ark-architect` judgment cards. `notAScore`, never a
|
|
165
|
+
verdict/`designFitness` input; there is no apply path.
|
|
166
|
+
|
|
157
167
|
**Governance weight (W02):** `contractHealth.governanceWeight` reports raw facts (layers, rules,
|
|
158
168
|
governed files, files/layer, rules/layer) plus a fixed band (`heavy` / `typical` / `light` /
|
|
159
169
|
`unknown`) with fixed wording. It is explicitly `notAScore` — never a gate input. Read `heavy` as
|
package/docs/package-surface.md
CHANGED
|
@@ -23,9 +23,10 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
|
|
|
23
23
|
| **Plan pattern B (P03+)** | `ark-check --plan --json` → `plan.patternBets[]`, `plan.goal.designWeak` | Additive. Each bet: `id`, `smellId`, `pilot`, `evidence`, `successSignal`, `killSwitch`, **`neverMechanicalSafe: true`**, `class: "judgment"`. **Never** auto-applied by loop/autoPatch; not a `remediationKind` mechanical-safe. `goal.met` remains edge honesty only. |
|
|
24
24
|
| **Pilot loop (Q04)** | `plan.pilotLoop` / `doctor.pilotLoop` | Additive. When design-weak: `active`, `oneAtATime`, `neverMechanicalSafe`, **`nextPilot`** extraction-card fields (`pilotTarget`, `smellId`, `move`, `successSignal`, `killSwitch`, `doNot[]`). **One pilot → re-doctor**; never multi-pilot batch; never mechanical-safe. |
|
|
25
25
|
| **AI-velocity eval (Q05)** | `npm run eval:ai-velocity` → `eval/ai-velocity-report.json` | Fixture-measured (no live LLM). Same feature scenario on design-weak vs golden-path arms; metric **`placementTurns`** (agent-equivalent). Golden must be strictly better. Method string lives next to the number. Does not weaken the gate. |
|
|
26
|
-
| **Contract health (W01)** | `ark-check --doctor --json` → `doctor.contractHealth`; optional `.ark/contract-smell-acks.json` | Additive, **advisory only** — meta-lint of the contract itself (layer-name heuristics; imprecision costs a warning line, never a verdict); never changes the verdict, `designFitness`, or `patternBets`. Stable smell ids: `contract-bidirectional-allow`, `contract-peripheral-depends-core`, `contract-lateral-adapter-allow`, `contract-dead-rule`; each smell has `severity`, `evidence[]` (sorted, honest `…(+N more)` truncation), `fix`, `message`, plain-language `outcome`, and `acknowledgedEdges` (acks applied to that id). **X03**: the lateral smell does not fire on an adapter reaching its **own family's infra base**
|
|
26
|
+
| **Contract health (W01)** | `ark-check --doctor --json` → `doctor.contractHealth`; optional `.ark/contract-smell-acks.json` | Additive, **advisory only** — meta-lint of the contract itself (layer-name heuristics; imprecision costs a warning line, never a verdict); never changes the verdict, `designFitness`, or `patternBets`. Stable smell ids: `contract-bidirectional-allow`, `contract-peripheral-depends-core`, `contract-lateral-adapter-allow`, `contract-dead-rule`; each smell has `severity`, `evidence[]` (sorted, honest `…(+N more)` truncation), `fix`, `message`, plain-language `outcome`, and `acknowledgedEdges` (acks applied to that id). **X03/X06**: the lateral smell does not fire on an adapter reaching its **own family's infra base** — the target reads `<Family><InfraWords…>` (**every** remaining target token an infra word: `Infra(structure)`/`Base`/`Core`/`Shared`/`Common`/`Kernel`/`Platform`/`Foundation`) and the source carries the family token **anywhere** in its name (X06, field: `HoursPersistenceAdapters -> PersistenceInfrastructure` — mid-name families). `PaymentsCoreAdapters` is still a sibling; cross-family edges, non-infra siblings, and the reverse (base → member) still fire. Acknowledgments live in the bounded sidecar (`{ acks: [{ id, edge, reason, reviewBy? }] }`, ≤64 KB / ≤200 entries; bidirectional edges order-insensitive, exact two segments); `contractHealth.acknowledged` counts **applied** acks only (stale acks count 0). **X02 ack lifecycle**: optional `reviewBy` (`YYYY-MM-DD`, strict round-trip validation — `2026-02-30` is malformed) — past the date the ack **stops applying** and the smell returns with `(ack expired …)` annotated evidence; among dated entries a fresh re-ack wins over a dead one, but once ANY dated ack exists for an edge the dated entries govern — a leftover undated duplicate cannot resurrect an expired exception. `detectContractSmells` defaults `today` to the real clock (pass `null` to disable expiry); `analyzeContractSmells` stays pure (clock injected). `contractHealth.ackLifecycle` reports `{ undated, malformed, expiredCount, expired[], staleCount, stale[] (lists capped at 12) }`; undated acks apply (backward compatible) but surface in doctor, report, and the fossilization note even when every smell is suppressed. **X05**: an ack matching **no detected edge** (orphaned by a fixed contract or quieted heuristic, unknown id, or typo'd edge) is `stale` — it suppresses nothing and doctor/report list the exact entries to fix or delete, even at zero visible smells. Malformed `reviewBy` never applies (fail-loud, like a sloppy edge); non-string `reviewBy` → whole file `invalid`. **Absent is normal**; malformed file or edge grammar → ignored + `ackFile.invalid` where applicable, never silent suppression. |
|
|
27
27
|
| **Effect capabilities (U03)** | Analysis API `collectCapabilityUses(ts, sourceFile)` + Domain vocabulary (`CAPABILITY_IDS`, `capabilityForModuleSpecifier`, `capabilityForAmbientName`, `lowerForbiddenGlobal`); pure-engine `ir.capabilityUses` | Additive within IR `1.0`. Seven **closed** ids: `network`, `filesystem`, `clock`, `randomness`, `environment`, `process`, `persistence` (ADR 0009). Direct evidence only — transitive inference never detects. The symbol-aware collector covers ambient globals (shadowing/type-only/globalThis-alias precision from the S05/C04 machinery) plus imports; the compiler-free IR engine carries **import-based** uses only (exact module or subpath match, never substring; textual `import type`/`export type` erasure — ANY braced named-binding list stays a value import there; template-literal bodies are skipped entirely (specifiers inside `${…}` are the symbol path's job); `require(…)` counts as capability evidence but never creates a graph edge on the pure path). **U04 walls are opt-in:** per-layer `capabilities: { deny: [...] }` or the dual-depth sugar `pure: true` (denies all seven); absence changes no verdict. `CAPABILITY_VIOLATION` is judgment-class (never mechanical-safe) with a port-injection `nextAction`; D7 dedup — an ambient use covered by the layer's `forbiddenGlobals` reports only `FORBIDDEN_GLOBAL`. Atomic preflight blocks denied capabilities across a complete multi-file candidate (import-based on the pure path; ambient adds on the symbol-aware CLI/hook path). T01 policy-delta classifies the ambient surface on **coverage atoms** (`ambient:<entry>` prefix-expanded + `import:<capability>`): any lost atom is weakening (`fetch`→`XMLHttpRequest`, `Date`→`Date.now`, wall→fg all weaken; finding path `$.layers[name].capabilities`); fg → equivalent-or-stronger wall never needs an acknowledgment; unlowerable custom globals keep raw key comparison. |
|
|
28
28
|
| **Ambient state (U05)** | `ark-check --doctor --json` → `doctor.ambientState`; optional `.ark/ambient-state-acks.json` | Additive, **advisory only and opt-in**: only layers declared `pure: true` are scanned; the MVP shape is module-scope `let`/`var`. Findings carry `file`/`line`/`name`/`kind` (sorted, capped with honest `truncated` count). Acknowledgments live in the bounded sidecar (`{ acks: [{ file, name, reason }] }`, ≤64 KB / ≤200 entries); `acknowledged` counts applied acks; malformed file suppresses nothing. When TypeScript is unavailable the sensor reports `available: false` instead of guessing. **No strict mode exists** — A5: strictness requires a completed corpus and an explicit later decision. |
|
|
29
|
+
| **Physical cohesion + reshape pilot (X04)** | `ark-check --doctor --json` → `doctor.physicalCohesion` (incl. `reshapePilot`); report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). |
|
|
29
30
|
| **Capability walls, every adapter (U04+U06)** | CLI scan, pure IR engine, atomic preflight, `ark-mcp --hook` / MCP gate (`capabilityWalls`), ESLint `ark/no-denied-capabilities` | The same opt-in deny set enforces across every surface: hook/MCP and CLI cover ambient + import evidence (symbol-aware); the pure engine, preflight, and ESLint cover the import dimension (documented envelope). Dual depth everywhere: plain port hint (`FIX_HINTS`/`suggestion`) + stable JSON (`ruleId`, `capability`, `fixClass: inject-port`, deterministic `nextAction`). |
|
|
30
31
|
| **Hook-path budgets (U06)** | `npm run bench:hook-path`; `eval/performance/hook-budgets.v1.json`; CI job "Hook-path end-to-end budgets" | Measures the COMPLETE pre-tool paths as fresh child processes (hook cold/warm, doctor cold) at 1k/10k. D5 method locked: ceilings are Linux-baseline p95 + fixed headroom, set once per cycle, never ratcheted; scenarios without a recorded baseline stay in RECORDING mode and cannot fail CI. |
|
|
31
32
|
| **Governance weight (W02)** | `ark-check --doctor --json` → `doctor.contractHealth.governanceWeight` | Additive, **advisory only** — raw facts (`declaredLayers`, `populatedLayers`, `governedFiles`, `rules`, `deniedEdges`, `allowedEdges`, `filesPerLayer`, `rulesPerLayer`) plus a fixed comparative band `weight: heavy | typical | light | unknown` and its fixed `note`. Fixed deterministic thresholds: **heavy** = fewer than 25 governed files per declared layer AND (6+ layers OR 4+ well-formed rules per layer); **light** = at most 2 layers over 150+ governed files; **unknown** = no layers or no governed files; everything else is **typical** (banding uses raw ratios; the reported ratios are rounded for display). `notAScore: true` is explicit: never a composite score, ranking, or gate input; the heavy note asks to justify NEW layers/rules and never suggests deleting working ones. Human doctor prints a line only for `heavy`/`light`. |
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/pedroknigge/arkgate",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.6.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "arkgate",
|
|
14
|
-
"version": "3.
|
|
14
|
+
"version": "3.6.0",
|
|
15
15
|
"runtimeHint": "npx",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
@@ -112,6 +112,15 @@ the same files or weaken the gate.
|
|
|
112
112
|
- Default to smallest viable phase 1; unlock phase 2 only when the user describes need.
|
|
113
113
|
- All user-facing copy is **English**.
|
|
114
114
|
|
|
115
|
+
## Merge cards (X04 reshape — judgment only)
|
|
116
|
+
|
|
117
|
+
When `doctor.physicalCohesion` reports a mirrored concept and the user asks whether files
|
|
118
|
+
should be **merged**, treat it as domain modeling, never deduplication (field fact: zero
|
|
119
|
+
structural clones among 123 same-concept files). Produce a **merge card** per candidate group:
|
|
120
|
+
which files, the domain concept they express, 2–3 shapes the merged module could take, and what
|
|
121
|
+
each shape costs — **no default action, no auto-merge, never a codemod**. Physical **moves**
|
|
122
|
+
belong to `/ark-loop`'s pilot loop; your job here is the judgment about what the concept IS.
|
|
123
|
+
|
|
115
124
|
## Verify and report
|
|
116
125
|
|
|
117
126
|
End with `ark-check --root . --config ark.config.json --strict-config` when the
|
|
@@ -105,6 +105,13 @@ If the “fix” is really a missing business intent or Domain home for a rule:
|
|
|
105
105
|
- Prefer mechanical-safe kinds when the plan tags them; otherwise design judgment carefully.
|
|
106
106
|
- Code only — no DB migrations unless user asked.
|
|
107
107
|
|
|
108
|
+
## Reshape findings (X04 — never mechanical)
|
|
109
|
+
|
|
110
|
+
If `doctor.physicalCohesion` fires while you fix: do **not** fold reshape moves into your fix
|
|
111
|
+
batch. Physical moves run only through `/ark-loop`'s one-pilot loop; merge decisions only as
|
|
112
|
+
`/ark-architect` merge cards. A cohesion finding is context for your fix, never a license to
|
|
113
|
+
reorganize.
|
|
114
|
+
|
|
108
115
|
## Done
|
|
109
116
|
|
|
110
117
|
- Targeted violations gone; no new ones.
|
|
@@ -82,6 +82,22 @@ feature dirs, plan clusters), you **may** dispatch **subagents**:
|
|
|
82
82
|
|
|
83
83
|
Never auto: free value uses of imports, multi-import files, dynamic import/require, forbidden globals, cycles, port-proof inject, multi-file adapter scaffolding without proof.
|
|
84
84
|
|
|
85
|
+
## Reshape pilots (X04 — physical cohesion, advisory)
|
|
86
|
+
|
|
87
|
+
When `ark-check --doctor --json` carries `doctor.physicalCohesion.reshapePilot.nextPilot`,
|
|
88
|
+
you may run **that one pilot** — never more:
|
|
89
|
+
|
|
90
|
+
1. Read the card: `pilotTarget`, `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`.
|
|
91
|
+
2. Moves are **proposed only** — enumerate the full move set for the pilot anchor, express it as
|
|
92
|
+
an architecture change map, and validate through the atomic preflight (`ark_prepare_change` /
|
|
93
|
+
the write gate) **before** any file moves. A move the preflight rejects is a finding, not a
|
|
94
|
+
thing to force.
|
|
95
|
+
3. Never move anything under `app/` or `pages/` (fixed by framework convention). Never merge
|
|
96
|
+
files here — merges are judgment cards for `/ark-architect` / `/ark-fix`.
|
|
97
|
+
4. After the move set: full gate re-run + re-doctor. Success = the concept's cluster count drops
|
|
98
|
+
and the verdict stays green; otherwise use the kill switch (revert the move set, nothing else).
|
|
99
|
+
5. Re-doctor decides whether a next card exists. One pilot per loop iteration, always.
|
|
100
|
+
|
|
85
101
|
## Steps
|
|
86
102
|
|
|
87
103
|
1. **Plan** — `ark-check --plan --json` (+ `--baseline` if used). If `goal.met`: stop **A**;
|