arkgate 2.8.3 → 2.9.1
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 +80 -0
- package/README.md +2 -1
- package/bin/ark-check.mjs +21 -3
- package/bin/ark-layer-match.mjs +88 -5
- package/bin/ark-mcp.mjs +7 -70
- package/bin/ark-shared.mjs +88 -8
- package/bin/ark.mjs +134 -22
- package/bin/lib/agent-gates.mjs +37 -3
- package/bin/lib/architecture-scan.mjs +16 -4
- package/bin/lib/config-warnings.mjs +11 -2
- package/bin/lib/doctor-plan.mjs +20 -0
- package/bin/lib/field-install.mjs +334 -0
- package/bin/lib/import-resolve.mjs +133 -0
- package/bin/lib/presets.mjs +207 -11
- package/bin/lib/remediation.mjs +15 -0
- package/bin/lib/suggestions.mjs +8 -3
- package/dist/eslint/index.cjs +63 -5
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +33 -1
- package/dist/eslint/index.d.ts +33 -1
- package/dist/eslint/index.js +63 -5
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +103 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +103 -14
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +78 -4
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +78 -4
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +103 -14
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +103 -14
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-CSJhEOk2.d.cts → types-D6Q8WHes.d.cts} +7 -0
- package/dist/{types-CSJhEOk2.d.ts → types-D6Q8WHes.d.ts} +7 -0
- package/docs/agent-guide.md +55 -4
- package/package.json +3 -1
- package/server.json +2 -2
- package/templates/architecture-playbook.json +65 -1
- package/templates/policy-packs/enthusiast-ddd-bounded-contexts.json +19 -0
- package/templates/policy-packs/enthusiast-ui-surface.json +18 -0
- package/templates/policy-packs/enthusiast-vertical-slice.json +18 -0
- package/templates/skills/ark-adopt.md +4 -0
- package/templates/skills/ark-architect.md +5 -1
- package/templates/skills/ark-autopilot.md +10 -1
- package/templates/skills/ark-fix.md +3 -0
- package/templates/skills/ark-place.md +7 -0
- package/templates/skills/ark-think.md +43 -0
package/dist/runtime/index.cjs
CHANGED
|
@@ -80,7 +80,7 @@ __export(runtime_exports, {
|
|
|
80
80
|
module.exports = __toCommonJS(runtime_exports);
|
|
81
81
|
|
|
82
82
|
// src/version.ts
|
|
83
|
-
var version = "2.
|
|
83
|
+
var version = "2.9.1";
|
|
84
84
|
|
|
85
85
|
// src/kernel/intent/IntentRegistry.ts
|
|
86
86
|
var IntentRegistry = class {
|
|
@@ -743,6 +743,82 @@ async function recordInterceptorError(interceptor, event, error, deps) {
|
|
|
743
743
|
});
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
+
// src/domain/layerMatch.ts
|
|
747
|
+
function normalizeGlobSeparators(pattern) {
|
|
748
|
+
let out = "";
|
|
749
|
+
for (let i = 0; i < pattern.length; i += 1) {
|
|
750
|
+
const c = pattern[i];
|
|
751
|
+
if (c === "\\" && i + 1 < pattern.length) {
|
|
752
|
+
const next = pattern[i + 1];
|
|
753
|
+
if ("*?{}[],".includes(next) || next === "\\") {
|
|
754
|
+
out += "\\" + next;
|
|
755
|
+
i += 1;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
out += "/";
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
out += c;
|
|
762
|
+
}
|
|
763
|
+
return out;
|
|
764
|
+
}
|
|
765
|
+
function sliceIdForPath(relPath, sliceFolders) {
|
|
766
|
+
if (!sliceFolders?.length) return void 0;
|
|
767
|
+
const parts = String(relPath).split(/[/\\]/).filter(Boolean);
|
|
768
|
+
const folders = new Set(sliceFolders.map((s) => String(s).toLowerCase()));
|
|
769
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
770
|
+
if (folders.has(parts[i].toLowerCase())) {
|
|
771
|
+
return `${parts[i]}/${parts[i + 1]}`;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return void 0;
|
|
775
|
+
}
|
|
776
|
+
function inferSliceFoldersFromPatterns(patterns) {
|
|
777
|
+
const out = /* @__PURE__ */ new Set();
|
|
778
|
+
for (const pattern of patterns ?? []) {
|
|
779
|
+
const glob = normalizeGlobSeparators(String(pattern));
|
|
780
|
+
const parts = glob.split("/").filter(Boolean);
|
|
781
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
782
|
+
const part = parts[i];
|
|
783
|
+
if ((part === "**" || part === "*") && i > 0) {
|
|
784
|
+
const prev = parts[i - 1];
|
|
785
|
+
if (prev && !prev.includes("*") && !prev.includes("{") && !prev.includes("}")) {
|
|
786
|
+
out.add(prev);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return [...out];
|
|
792
|
+
}
|
|
793
|
+
function resolveSliceFolders(rule, layerName, layers) {
|
|
794
|
+
if (Array.isArray(rule.sliceFolders) && rule.sliceFolders.length > 0) {
|
|
795
|
+
return rule.sliceFolders.filter((s) => typeof s === "string" && s.length > 0);
|
|
796
|
+
}
|
|
797
|
+
const layer = (layers ?? []).find((l) => l.name === layerName);
|
|
798
|
+
return inferSliceFoldersFromPatterns(layer?.patterns);
|
|
799
|
+
}
|
|
800
|
+
function findDeniedEdgeRule(rules, from, to, options) {
|
|
801
|
+
for (const rule of rules ?? []) {
|
|
802
|
+
if (rule.from !== from || rule.to !== to) continue;
|
|
803
|
+
if (rule.allowed !== false) continue;
|
|
804
|
+
if (rule.peerIsolation) {
|
|
805
|
+
const fromPath = options?.fromPath;
|
|
806
|
+
const toPath = options?.toPath;
|
|
807
|
+
if (!fromPath || !toPath) continue;
|
|
808
|
+
const folders = resolveSliceFolders(rule, from, options?.layers);
|
|
809
|
+
if (folders.length === 0) continue;
|
|
810
|
+
const fromSlice = sliceIdForPath(fromPath, folders);
|
|
811
|
+
const toSlice = sliceIdForPath(toPath, folders);
|
|
812
|
+
if (!fromSlice || !toSlice) continue;
|
|
813
|
+
if (fromSlice !== toSlice) return rule;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (from === to) continue;
|
|
817
|
+
return rule;
|
|
818
|
+
}
|
|
819
|
+
return void 0;
|
|
820
|
+
}
|
|
821
|
+
|
|
746
822
|
// src/kernel/event-bus/observedLayerFlow.ts
|
|
747
823
|
async function assertObservedLayerFlowAllowed(event, deps) {
|
|
748
824
|
if (deps.mode === "off" || !deps.architectureProfile) {
|
|
@@ -754,9 +830,7 @@ async function assertObservedLayerFlowAllowed(event, deps) {
|
|
|
754
830
|
const fromLayer = profile.resolveLayer(source);
|
|
755
831
|
const toLayer = profile.resolveLayer(event.intent);
|
|
756
832
|
if (!fromLayer || !toLayer) return;
|
|
757
|
-
const blocked = profile.rules
|
|
758
|
-
(rule) => !rule.allowed && rule.from === fromLayer && rule.to === toLayer
|
|
759
|
-
);
|
|
833
|
+
const blocked = findDeniedEdgeRule(profile.rules, fromLayer, toLayer);
|
|
760
834
|
if (!blocked) return;
|
|
761
835
|
const severity = deps.mode;
|
|
762
836
|
const message = blocked.message ?? `Observed layer violation: "${source}" (${fromLayer}) must not produce "${event.intent}" (${toLayer}).`;
|
|
@@ -2406,16 +2480,26 @@ function createAICodeGate(options = {}) {
|
|
|
2406
2480
|
}
|
|
2407
2481
|
}
|
|
2408
2482
|
for (const specifier of extractModuleSpecifiers(source)) {
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2483
|
+
const targetHit = options.resolveImportTarget?.(specifier.value, filePath) ?? (options.resolveImportLayer ? { layer: options.resolveImportLayer(specifier.value, filePath) } : void 0);
|
|
2484
|
+
const sourceHit = typeof filePath === "string" ? options.resolveImportTarget?.(filePath) ?? (options.resolveImportLayer ? { layer: contextLayer, relPath: void 0 } : void 0) : void 0;
|
|
2485
|
+
const targetLayer = targetHit?.layer;
|
|
2486
|
+
if (targetLayer && contextLayer) {
|
|
2487
|
+
const blocked = findDeniedEdgeRule(
|
|
2488
|
+
options.architectureProfile?.rules,
|
|
2489
|
+
contextLayer,
|
|
2490
|
+
targetLayer,
|
|
2491
|
+
{
|
|
2492
|
+
fromPath: sourceHit?.relPath,
|
|
2493
|
+
toPath: targetHit?.relPath,
|
|
2494
|
+
layers: options.architectureLayers
|
|
2495
|
+
}
|
|
2413
2496
|
);
|
|
2414
2497
|
if (blocked) {
|
|
2498
|
+
const peer = Boolean(blocked.peerIsolation);
|
|
2415
2499
|
violations.push(
|
|
2416
2500
|
violation(
|
|
2417
2501
|
"LAYER_IMPORT_VIOLATION",
|
|
2418
|
-
blocked.message ?? `Layer "${contextLayer}" must not import "${targetLayer}"
|
|
2502
|
+
blocked.message ?? (peer ? `Layer "${contextLayer}" must not import across slices into "${targetLayer}".` : `Layer "${contextLayer}" must not import "${targetLayer}".`),
|
|
2419
2503
|
{
|
|
2420
2504
|
line: lineOf(source, specifier.index),
|
|
2421
2505
|
source: specifier.value,
|
|
@@ -2423,13 +2507,16 @@ function createAICodeGate(options = {}) {
|
|
|
2423
2507
|
filePath,
|
|
2424
2508
|
fromLayer: contextLayer,
|
|
2425
2509
|
toLayer: targetLayer,
|
|
2426
|
-
suggestion: "Depend on a port/interface owned by an inner layer instead, or move this code to a layer allowed to make this import.",
|
|
2427
|
-
details: { importKind: specifier.kind }
|
|
2510
|
+
suggestion: peer ? "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.",
|
|
2511
|
+
details: { importKind: specifier.kind, peerIsolation: peer }
|
|
2428
2512
|
}
|
|
2429
2513
|
)
|
|
2430
2514
|
);
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
if (targetLayer !== contextLayer) {
|
|
2518
|
+
continue;
|
|
2431
2519
|
}
|
|
2432
|
-
continue;
|
|
2433
2520
|
}
|
|
2434
2521
|
if (exemptFromInfraHeuristics) continue;
|
|
2435
2522
|
if (!hasInfrastructureToken(specifier.value) && !isKnownInfrastructurePackage(specifier.value)) {
|
|
@@ -2498,8 +2585,10 @@ function createAICodeGate(options = {}) {
|
|
|
2498
2585
|
if (!looksLikeIntentName(literal.value)) continue;
|
|
2499
2586
|
const targetLayer = options.architectureProfile.resolveLayer(literal.value);
|
|
2500
2587
|
if (!targetLayer) continue;
|
|
2501
|
-
const blocked =
|
|
2502
|
-
|
|
2588
|
+
const blocked = findDeniedEdgeRule(
|
|
2589
|
+
options.architectureProfile.rules,
|
|
2590
|
+
contextLayer,
|
|
2591
|
+
targetLayer
|
|
2503
2592
|
);
|
|
2504
2593
|
if (blocked) {
|
|
2505
2594
|
violations.push(
|