@wairon/cli 5.0.2-dev.12 → 5.0.2-dev.14
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/dist/cli/index.js +1661 -724
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +903 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.0.2-dev.
|
|
68
|
+
WAIRON_VERSION = "5.0.2-dev.14";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -1914,6 +1914,47 @@ var init_loader = __esm({
|
|
|
1914
1914
|
}
|
|
1915
1915
|
});
|
|
1916
1916
|
|
|
1917
|
+
// src/core/statehash.ts
|
|
1918
|
+
function computeStateId() {
|
|
1919
|
+
const tree = {
|
|
1920
|
+
system: loadSystemSpec(),
|
|
1921
|
+
subsystems: loadSubsystemSpecs(),
|
|
1922
|
+
components: loadComponentSpecs(),
|
|
1923
|
+
interfaces: loadInterfaceSpecs(),
|
|
1924
|
+
implementations: loadImplementationSpecs(),
|
|
1925
|
+
types: loadTypeSpecs()
|
|
1926
|
+
};
|
|
1927
|
+
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
1928
|
+
return { algorithm: "sha256", digest };
|
|
1929
|
+
}
|
|
1930
|
+
function stateIdEquals(a, b) {
|
|
1931
|
+
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
1932
|
+
}
|
|
1933
|
+
function canonicalize(value) {
|
|
1934
|
+
return JSON.stringify(sortKeys(value));
|
|
1935
|
+
}
|
|
1936
|
+
function sortKeys(v) {
|
|
1937
|
+
if (Array.isArray(v)) return v.map(sortKeys);
|
|
1938
|
+
if (v && typeof v === "object") {
|
|
1939
|
+
const src = v;
|
|
1940
|
+
const out = {};
|
|
1941
|
+
for (const k of Object.keys(src).sort()) {
|
|
1942
|
+
if (k === "createdAt" || k === "updatedAt") continue;
|
|
1943
|
+
out[k] = sortKeys(src[k]);
|
|
1944
|
+
}
|
|
1945
|
+
return out;
|
|
1946
|
+
}
|
|
1947
|
+
return v;
|
|
1948
|
+
}
|
|
1949
|
+
var crypto;
|
|
1950
|
+
var init_statehash = __esm({
|
|
1951
|
+
"src/core/statehash.ts"() {
|
|
1952
|
+
"use strict";
|
|
1953
|
+
crypto = __toESM(require("crypto"));
|
|
1954
|
+
init_specs2();
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1917
1958
|
// src/core/narrative-labels.ts
|
|
1918
1959
|
function resolveNarrativeLabels(methodName, steps) {
|
|
1919
1960
|
const errors = [];
|
|
@@ -2999,6 +3040,16 @@ header input[type="search"]::placeholder { color:var(--dim); }
|
|
|
2999
3040
|
#moreMenu .dropdown { display:block; width:100%; }
|
|
3000
3041
|
#moreMenu .dropdown > .tbtn, #moreMenu > .tbtn { display:block; width:100%; text-align:left; border:none; background:transparent; margin:2px 0; }
|
|
3001
3042
|
#moreMenu .dropdown > .tbtn:hover, #moreMenu > .tbtn:hover { background:var(--hover-bg); }
|
|
3043
|
+
/* Staged header compaction (responsive only, never persisted): compact
|
|
3044
|
+
stand-ins for the search input, the mode tabs, and the ancestor crumbs.
|
|
3045
|
+
All hidden at full width, so a roomy header renders exactly as before. */
|
|
3046
|
+
#searchBtn { position:relative; }
|
|
3047
|
+
#searchBtn.hasq::after { content:''; position:absolute; top:3px; right:3px; width:7px; height:7px; border-radius:50%; background:var(--accent); }
|
|
3048
|
+
.search-menu { min-width:210px; padding:8px; }
|
|
3049
|
+
.search-menu input[type="search"] { width:100%; }
|
|
3050
|
+
#modeMenu button.active { background:var(--accent); color:#fff; font-weight:700; }
|
|
3051
|
+
#crumbs .dropdown { display:inline-flex; }
|
|
3052
|
+
#crumbs .crumbmore { font-weight:700; }
|
|
3002
3053
|
|
|
3003
3054
|
/* Settings panel \u2014 toggle switches */
|
|
3004
3055
|
.settings-menu { min-width:266px; }
|
|
@@ -3122,9 +3173,17 @@ body.presentation #exitPresent, body.presentation #presentDetails { display:bloc
|
|
|
3122
3173
|
<button data-vm="types">Types</button>
|
|
3123
3174
|
<button data-vm="databases">Databases</button>
|
|
3124
3175
|
</div>
|
|
3176
|
+
<div class="dropdown" id="modeDd" style="display:none">
|
|
3177
|
+
<button class="tbtn" id="modeBtn" title="Switch between the component architecture, the type ERD, or the database schemas">Components \u25BE</button>
|
|
3178
|
+
<div class="menu" id="modeMenu"></div>
|
|
3179
|
+
</div>
|
|
3125
3180
|
<nav id="crumbs"></nav>
|
|
3126
3181
|
<span class="divider"></span>
|
|
3127
3182
|
<input id="search" type="search" placeholder="Search this view\u2026">
|
|
3183
|
+
<div class="dropdown" id="searchDd" style="display:none">
|
|
3184
|
+
<button class="tbtn" id="searchBtn" title="Search this view">\u{1F50D}</button>
|
|
3185
|
+
<div class="menu search-menu" id="searchMenu"></div>
|
|
3186
|
+
</div>
|
|
3128
3187
|
<div class="seg" id="typesDetailSeg" style="display:none" title="ERD detail level">
|
|
3129
3188
|
<button data-td="full">Full</button>
|
|
3130
3189
|
<button data-td="fields">Fields</button>
|
|
@@ -3641,13 +3700,300 @@ var MODEL = __MODEL_JSON__;
|
|
|
3641
3700
|
var BOX_W = 200, BOX_H = 56, SUBBOX_W = 230, SUBBOX_H = 84, GAP_X = 110, GAP_Y = 34;
|
|
3642
3701
|
var INNER_W = 130, INNER_H = 36, INNER_GAPX = 26, INNER_GAPY = 12, HEAD_H = 34, PADI = 14;
|
|
3643
3702
|
|
|
3703
|
+
// ---- deep expansion (opt-in via subsystem.deepInternals) -------------------
|
|
3704
|
+
// When Internals is on and a subsystem record carries deepInternals:true, its
|
|
3705
|
+
// box renders its WHOLE subtree instead of one layer: deep-flagged child
|
|
3706
|
+
// subsystems become NESTED boundary boxes (recursing, defensively capped),
|
|
3707
|
+
// every other child renders as a fixed leaf tile that is never expanded
|
|
3708
|
+
// further. Sizes are computed bottom-up (a nested container tile takes its
|
|
3709
|
+
// recursive {w,h}); positions are emitted top-down by accumulating parent
|
|
3710
|
+
// top-left offsets. Relations between concrete visible endpoints are drawn
|
|
3711
|
+
// as ONE direct line each by buildDeepContext \u2014 crossing nested boundaries
|
|
3712
|
+
// on purpose (the full org overview of project relations); only relations
|
|
3713
|
+
// that cannot resolve to two concrete endpoints keep today's port machinery,
|
|
3714
|
+
// and ONLY at the outermost box. Without the flag this whole path is inert
|
|
3715
|
+
// and the classic one-layer innerLayout runs unchanged.
|
|
3716
|
+
var DEEP_MAX_DEPTH = 6;
|
|
3717
|
+
function isDeepId(subId) {
|
|
3718
|
+
var s = subById[subId];
|
|
3719
|
+
return !!(s && s.deepInternals);
|
|
3720
|
+
}
|
|
3721
|
+
// The DEEPEST visible tile representing compId inside the deep-expanded box
|
|
3722
|
+
// rooted at rootSubId: descend deep-flagged containers (the same expansion
|
|
3723
|
+
// rule as deepContainerLayout) until the containing child is a leaf tile.
|
|
3724
|
+
// Returns the child entry { kind, id } (its node id is IN(kind, id)).
|
|
3725
|
+
function deepLeafFor(compId, rootSubId) {
|
|
3726
|
+
var subId = rootSubId, depth = 1;
|
|
3727
|
+
for (;;) {
|
|
3728
|
+
var child = childOfScopeContaining(compId, { kind: 'subsystem', id: subId });
|
|
3729
|
+
if (!child) return null;
|
|
3730
|
+
if (child.kind === 'subsystem' && isDeepId(child.id) && depth < DEEP_MAX_DEPTH) {
|
|
3731
|
+
subId = child.id; depth += 1;
|
|
3732
|
+
continue;
|
|
3733
|
+
}
|
|
3734
|
+
return child;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
// One deep container's DIRECT children, placed with PER-TILE sizes (nested
|
|
3738
|
+
// containers take their recursive size; leaves stay INNER_W x INNER_H). The
|
|
3739
|
+
// placement mirrors innerLayout's strategy switch, generalised to variable
|
|
3740
|
+
// tile sizes. Tiles are centres relative to THIS container's top-left corner
|
|
3741
|
+
// (content sits right of PADI, below the HEAD_H label band); nested tiles
|
|
3742
|
+
// are relative to their own container, so emission accumulates offsets.
|
|
3743
|
+
function deepContainerLayout(subId, depth) {
|
|
3744
|
+
var kids = childrenOf({ kind: 'subsystem', id: subId });
|
|
3745
|
+
if (!kids.length) {
|
|
3746
|
+
// An EMPTY deep subsystem still shows as a (min-size) boundary box.
|
|
3747
|
+
return { tiles: [], w: INNER_W + 2 * PADI, h: HEAD_H + PADI };
|
|
3748
|
+
}
|
|
3749
|
+
var scope = { kind: 'subsystem', id: subId };
|
|
3750
|
+
var kidKey = function (k) { return k.kind + ':' + k.id; };
|
|
3751
|
+
var size = {};
|
|
3752
|
+
kids.forEach(function (k) {
|
|
3753
|
+
if (k.kind === 'subsystem' && isDeepId(k.id) && depth < DEEP_MAX_DEPTH) {
|
|
3754
|
+
var nested = deepContainerLayout(k.id, depth + 1);
|
|
3755
|
+
size[kidKey(k)] = { w: nested.w, h: nested.h, sub: nested };
|
|
3756
|
+
} else {
|
|
3757
|
+
size[kidKey(k)] = { w: INNER_W, h: INNER_H, sub: null };
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
// Intra-container edges lifted to DIRECT children \u2014 for LAYOUT ONLY (the
|
|
3761
|
+
// drawn lines come from buildDeepContext's direct pass, never per level).
|
|
3762
|
+
var intra = {};
|
|
3763
|
+
MODEL.edges.forEach(function (edge) {
|
|
3764
|
+
var a = childOfScopeContaining(edge.from, scope);
|
|
3765
|
+
var b = childOfScopeContaining(edge.to, scope);
|
|
3766
|
+
if (!a || !b) return;
|
|
3767
|
+
var ak = a.kind + ':' + a.id, bk = b.kind + ':' + b.id;
|
|
3768
|
+
if (!size[ak] || !size[bk] || ak === bk) return;
|
|
3769
|
+
intra[ak + '=>' + bk] = 1;
|
|
3770
|
+
});
|
|
3771
|
+
var layer = {};
|
|
3772
|
+
function calc(k, stack) {
|
|
3773
|
+
var key = kidKey(k);
|
|
3774
|
+
if (layer[key] !== undefined) return layer[key];
|
|
3775
|
+
if (stack[key]) return 0;
|
|
3776
|
+
stack[key] = 1;
|
|
3777
|
+
var l = 0;
|
|
3778
|
+
if (k.kind === 'component') {
|
|
3779
|
+
var c = compById[k.id];
|
|
3780
|
+
if (c && (c.componentType === 'Portal' || c.componentType === 'Observer')) { layer[key] = 0; delete stack[key]; return 0; }
|
|
3781
|
+
}
|
|
3782
|
+
Object.keys(intra).forEach(function (ek) {
|
|
3783
|
+
var cut = ek.indexOf('=>');
|
|
3784
|
+
if (ek.slice(cut + 2) !== key) return;
|
|
3785
|
+
var srcKid = kids.filter(function (x) { return kidKey(x) === ek.slice(0, cut); })[0];
|
|
3786
|
+
if (srcKid) l = Math.max(l, calc(srcKid, stack) + 1);
|
|
3787
|
+
});
|
|
3788
|
+
delete stack[key];
|
|
3789
|
+
layer[key] = l;
|
|
3790
|
+
return l;
|
|
3791
|
+
}
|
|
3792
|
+
kids.forEach(function (k) { calc(k, {}); });
|
|
3793
|
+
var tiles = [], contentW = 0, contentH = 0;
|
|
3794
|
+
if (state.layout === 'grid') {
|
|
3795
|
+
// Row packing by ACTUAL tile size (the fixed per-column grid assumed
|
|
3796
|
+
// uniform tiles); the target row width follows the tile count, widened
|
|
3797
|
+
// to at least the widest single tile.
|
|
3798
|
+
var gsorted = kids.slice().sort(function (a, b) { return a.id < b.id ? -1 : 1; });
|
|
3799
|
+
var target = Math.max(1, Math.ceil(Math.sqrt(kids.length))) * (INNER_W + INNER_GAPX);
|
|
3800
|
+
kids.forEach(function (k) { var s0 = size[kidKey(k)]; if (s0.w > target) target = s0.w; });
|
|
3801
|
+
var gx = 0, gy = 0, rowH = 0;
|
|
3802
|
+
gsorted.forEach(function (k) {
|
|
3803
|
+
var s = size[kidKey(k)];
|
|
3804
|
+
if (gx > 0 && gx + s.w > target) { gx = 0; gy += rowH + INNER_GAPY; rowH = 0; }
|
|
3805
|
+
tiles.push({ kid: k, x: gx + s.w / 2, y: gy + s.h / 2, w: s.w, h: s.h, sub: s.sub });
|
|
3806
|
+
gx += s.w + INNER_GAPX;
|
|
3807
|
+
if (s.h > rowH) rowH = s.h;
|
|
3808
|
+
if (gx - INNER_GAPX > contentW) contentW = gx - INNER_GAPX;
|
|
3809
|
+
if (gy + rowH > contentH) contentH = gy + rowH;
|
|
3810
|
+
});
|
|
3811
|
+
} else if (state.layout === 'concentric' || state.layout === 'force') {
|
|
3812
|
+
var ideg = {};
|
|
3813
|
+
kids.forEach(function (k) { ideg[kidKey(k)] = 0; });
|
|
3814
|
+
Object.keys(intra).forEach(function (ek) {
|
|
3815
|
+
var cut2 = ek.indexOf('=>');
|
|
3816
|
+
var sk = ek.slice(0, cut2), tk = ek.slice(cut2 + 2);
|
|
3817
|
+
if (ideg[sk] !== undefined) ideg[sk]++;
|
|
3818
|
+
if (ideg[tk] !== undefined) ideg[tk]++;
|
|
3819
|
+
});
|
|
3820
|
+
var rel = concentricPositions(
|
|
3821
|
+
kids.map(kidKey),
|
|
3822
|
+
function (key) { return ideg[key] || 0; },
|
|
3823
|
+
function (key) { return { w: size[key].w, h: size[key].h }; }
|
|
3824
|
+
);
|
|
3825
|
+
// Normalise by the tiles' BOUNDING BOX (not just the centres) so a wide
|
|
3826
|
+
// nested container on the rim still clears the container's left/top pad.
|
|
3827
|
+
var minL = Infinity, minT = Infinity;
|
|
3828
|
+
kids.forEach(function (k) {
|
|
3829
|
+
var s1 = size[kidKey(k)], p1 = rel[kidKey(k)] || { x: 0, y: 0 };
|
|
3830
|
+
if (p1.x - s1.w / 2 < minL) minL = p1.x - s1.w / 2;
|
|
3831
|
+
if (p1.y - s1.h / 2 < minT) minT = p1.y - s1.h / 2;
|
|
3832
|
+
});
|
|
3833
|
+
if (minL === Infinity) { minL = 0; minT = 0; }
|
|
3834
|
+
kids.forEach(function (k) {
|
|
3835
|
+
var s2 = size[kidKey(k)], p2 = rel[kidKey(k)] || { x: 0, y: 0 };
|
|
3836
|
+
var cx = p2.x - minL, cyy = p2.y - minT;
|
|
3837
|
+
tiles.push({ kid: k, x: cx, y: cyy, w: s2.w, h: s2.h, sub: s2.sub });
|
|
3838
|
+
if (cx + s2.w / 2 > contentW) contentW = cx + s2.w / 2;
|
|
3839
|
+
if (cyy + s2.h / 2 > contentH) contentH = cyy + s2.h / 2;
|
|
3840
|
+
});
|
|
3841
|
+
} else {
|
|
3842
|
+
// Layered dependency columns: the column is as wide as its widest tile,
|
|
3843
|
+
// and each tile advances by ITS OWN height.
|
|
3844
|
+
var cols = {};
|
|
3845
|
+
kids.forEach(function (k) { var l = layer[kidKey(k)] || 0; (cols[l] = cols[l] || []).push(k); });
|
|
3846
|
+
var colKeys = Object.keys(cols).map(Number).sort(function (a, b) { return a - b; });
|
|
3847
|
+
var x = 0;
|
|
3848
|
+
colKeys.forEach(function (ck) {
|
|
3849
|
+
var col = cols[ck].sort(function (a, b) { return a.id < b.id ? -1 : 1; });
|
|
3850
|
+
var colW = 0, y = 0;
|
|
3851
|
+
col.forEach(function (k) { var s3 = size[kidKey(k)]; if (s3.w > colW) colW = s3.w; });
|
|
3852
|
+
col.forEach(function (k) {
|
|
3853
|
+
var s = size[kidKey(k)];
|
|
3854
|
+
tiles.push({ kid: k, x: x + colW / 2, y: y + s.h / 2, w: s.w, h: s.h, sub: s.sub });
|
|
3855
|
+
y += s.h + INNER_GAPY;
|
|
3856
|
+
});
|
|
3857
|
+
if (y - INNER_GAPY > contentH) contentH = y - INNER_GAPY;
|
|
3858
|
+
x += colW + INNER_GAPX;
|
|
3859
|
+
});
|
|
3860
|
+
contentW = x - INNER_GAPX;
|
|
3861
|
+
}
|
|
3862
|
+
tiles.forEach(function (t) { t.x += PADI; t.y += HEAD_H; });
|
|
3863
|
+
return { tiles: tiles, w: contentW + 2 * PADI, h: HEAD_H + contentH + PADI };
|
|
3864
|
+
}
|
|
3865
|
+
// Top-level deep box: the recursive interior plus today's port machinery at
|
|
3866
|
+
// the OUTERMOST box only (buildDeepContext supplies which relations still
|
|
3867
|
+
// need ports; stubs run port <-> the DEEPEST visible leaf tile). Returns the
|
|
3868
|
+
// same shape as innerLayout, plus deep:true so emission recurses.
|
|
3869
|
+
function deepLayout(entry, portRec) {
|
|
3870
|
+
var box = deepContainerLayout(entry.id, 1);
|
|
3871
|
+
var parentId = anchorNodeId(entry);
|
|
3872
|
+
var pBaseIn = 'p~in~' + parentId + '~', pBaseOut = 'p~out~' + parentId + '~';
|
|
3873
|
+
var extIn = portRec ? portRec.extIn : {}, extOut = portRec ? portRec.extOut : {};
|
|
3874
|
+
var inIds = Object.keys(extIn).sort(), outIds = Object.keys(extOut).sort();
|
|
3875
|
+
var hasIn = inIds.length > 0, hasOut = outIds.length > 0;
|
|
3876
|
+
var PROXY_W = 22, PROXY_H = 22, PROXY_GAP = 8;
|
|
3877
|
+
var shift = hasIn ? PROXY_W + INNER_GAPX : 0;
|
|
3878
|
+
var tiles = box.tiles;
|
|
3879
|
+
if (shift) tiles.forEach(function (t) { t.x += shift; });
|
|
3880
|
+
var w = box.w + shift + (hasOut ? PROXY_W + INNER_GAPX : 0);
|
|
3881
|
+
if (w < SUBBOX_W) w = SUBBOX_W;
|
|
3882
|
+
var stackMax = Math.max(inIds.length, outIds.length);
|
|
3883
|
+
var h = Math.max(box.h, HEAD_H + stackMax * PROXY_H + Math.max(0, stackMax - 1) * PROXY_GAP + PADI);
|
|
3884
|
+
var midY = HEAD_H + Math.max(0, (h - HEAD_H - PADI) / 2);
|
|
3885
|
+
function stackPorts(ids, recs, base, cx, dir) {
|
|
3886
|
+
var total = ids.length * PROXY_H + Math.max(0, ids.length - 1) * PROXY_GAP;
|
|
3887
|
+
var y0 = Math.max(HEAD_H + PROXY_H / 2, midY - total / 2 + PROXY_H / 2);
|
|
3888
|
+
return ids.map(function (eid, i) {
|
|
3889
|
+
var km = recs[eid].kids, klist = [];
|
|
3890
|
+
Object.keys(km).forEach(function (key) { klist.push(km[key]); });
|
|
3891
|
+
return { id: base + eid, extId: eid, dir: dir, kids: klist, raws: Object.keys(recs[eid].raws), x: cx, y: y0 + i * (PROXY_H + PROXY_GAP), w: PROXY_W, h: PROXY_H };
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
return {
|
|
3895
|
+
deep: true,
|
|
3896
|
+
tiles: tiles,
|
|
3897
|
+
edges: portRec ? Object.keys(portRec.stubs).map(function (k) { return portRec.stubs[k]; }) : [],
|
|
3898
|
+
proxies: stackPorts(inIds, extIn, pBaseIn, PADI + PROXY_W / 2, 'in')
|
|
3899
|
+
.concat(stackPorts(outIds, extOut, pBaseOut, w - PADI - PROXY_W / 2, 'out')),
|
|
3900
|
+
w: w,
|
|
3901
|
+
h: h,
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
// View-level deep context (null unless Internals is on AND at least one
|
|
3905
|
+
// in-view entry is deep-flagged \u2014 the classic path never sees it): which
|
|
3906
|
+
// top-level entries are deep-expanded; every relation drawn as a DIRECT
|
|
3907
|
+
// concrete line (deduped per src=>tgt pair); the per-top-pair count used to
|
|
3908
|
+
// suppress aggregated edges whose constituents are ALL drawn directly; and
|
|
3909
|
+
// the port records for relations that keep today's port semantics.
|
|
3910
|
+
function buildDeepContext(scope, entries) {
|
|
3911
|
+
if (!state.internals) return null;
|
|
3912
|
+
var deepByAnchor = {}, any = false;
|
|
3913
|
+
entries.forEach(function (e) {
|
|
3914
|
+
if (e.kind === 'subsystem' && isDeepId(e.id)) { deepByAnchor[anchorNodeId(e)] = e; any = true; }
|
|
3915
|
+
});
|
|
3916
|
+
if (!any) return null;
|
|
3917
|
+
var entryByAnchor = {};
|
|
3918
|
+
entries.forEach(function (e) { entryByAnchor[e.kind + ':' + e.id] = e; });
|
|
3919
|
+
// A relation endpoint's DIRECT-line node in this view: the deepest leaf
|
|
3920
|
+
// tile inside a deep-expanded entry, or a top-level component box ITSELF.
|
|
3921
|
+
// null = this endpoint keeps aggregated/port semantics (interiors of
|
|
3922
|
+
// non-deep entries, out-of-scope counterparts).
|
|
3923
|
+
function directEnd(compId) {
|
|
3924
|
+
var child = childOfScopeContaining(compId, scope);
|
|
3925
|
+
var entry = child && entryByAnchor[child.kind + ':' + child.id];
|
|
3926
|
+
if (!entry) return null;
|
|
3927
|
+
var aid = anchorNodeId(entry);
|
|
3928
|
+
if (deepByAnchor[aid]) {
|
|
3929
|
+
var leaf = deepLeafFor(compId, entry.id);
|
|
3930
|
+
return leaf ? { node: IN(leaf.kind, leaf.id), top: aid, deep: true } : null;
|
|
3931
|
+
}
|
|
3932
|
+
if (entry.kind === 'component' && entry.id === compId) return { node: aid, top: aid, deep: false };
|
|
3933
|
+
return null;
|
|
3934
|
+
}
|
|
3935
|
+
var direct = {}, directTopCount = {}, ports = {};
|
|
3936
|
+
function portRec(aid) { return ports[aid] = ports[aid] || { extIn: {}, extOut: {}, stubs: {} }; }
|
|
3937
|
+
MODEL.edges.forEach(function (edge) {
|
|
3938
|
+
var a = directEnd(edge.from), b = directEnd(edge.to);
|
|
3939
|
+
if (a && b && (a.deep || b.deep) && a.node !== b.node) {
|
|
3940
|
+
// Drawn as ONE direct line \u2014 never ALSO as ports/stubs (dedupe rule).
|
|
3941
|
+
var key = a.node + '=>' + b.node;
|
|
3942
|
+
if (!direct[key]) direct[key] = { src: a.node, tgt: b.node, cross: false, aTop: a.top, bTop: b.top };
|
|
3943
|
+
if (edge.cross) direct[key].cross = true;
|
|
3944
|
+
if (a.top !== b.top) {
|
|
3945
|
+
var tk = a.top + '=>' + b.top;
|
|
3946
|
+
directTopCount[tk] = (directTopCount[tk] || 0) + 1;
|
|
3947
|
+
}
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
// Not a direct line: keep today's port semantics on any deep box with
|
|
3951
|
+
// exactly one endpoint inside its subtree, stubbed to the deepest leaf.
|
|
3952
|
+
var ac = childOfScopeContaining(edge.from, scope);
|
|
3953
|
+
var bc = childOfScopeContaining(edge.to, scope);
|
|
3954
|
+
var aEnt = ac && entryByAnchor[ac.kind + ':' + ac.id];
|
|
3955
|
+
var bEnt = bc && entryByAnchor[bc.kind + ':' + bc.id];
|
|
3956
|
+
var aAid = aEnt ? anchorNodeId(aEnt) : null;
|
|
3957
|
+
var bAid = bEnt ? anchorNodeId(bEnt) : null;
|
|
3958
|
+
if (aAid === bAid) return; // internal to one entry, or neither in scope
|
|
3959
|
+
if (aAid && deepByAnchor[aAid]) {
|
|
3960
|
+
var leafA = deepLeafFor(edge.from, aEnt.id);
|
|
3961
|
+
if (leafA) {
|
|
3962
|
+
var recA = portRec(aAid);
|
|
3963
|
+
var ro = recA.extOut[edge.to] = recA.extOut[edge.to] || { kids: {}, raws: {} };
|
|
3964
|
+
ro.kids[leafA.kind + ':' + leafA.id] = leafA;
|
|
3965
|
+
ro.raws[edge.from] = 1;
|
|
3966
|
+
var poId = 'p~out~' + aAid + '~' + edge.to;
|
|
3967
|
+
recA.stubs[IN(leafA.kind, leafA.id) + '=>' + poId] = { src: IN(leafA.kind, leafA.id), tgt: poId, stub: true };
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
if (bAid && deepByAnchor[bAid]) {
|
|
3971
|
+
var leafB = deepLeafFor(edge.to, bEnt.id);
|
|
3972
|
+
if (leafB) {
|
|
3973
|
+
var recB = portRec(bAid);
|
|
3974
|
+
var ri = recB.extIn[edge.from] = recB.extIn[edge.from] || { kids: {}, raws: {} };
|
|
3975
|
+
ri.kids[leafB.kind + ':' + leafB.id] = leafB;
|
|
3976
|
+
ri.raws[edge.to] = 1;
|
|
3977
|
+
var piId = 'p~in~' + bAid + '~' + edge.from;
|
|
3978
|
+
recB.stubs[piId + '=>' + IN(leafB.kind, leafB.id)] = { src: piId, tgt: IN(leafB.kind, leafB.id), stub: true };
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
});
|
|
3982
|
+
return { deepByAnchor: deepByAnchor, direct: direct, directTopCount: directTopCount, ports: ports };
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3644
3985
|
// Micro-layout for a container's direct children when Internals is on:
|
|
3645
3986
|
// layered mini columns + intra-container edges. Each external relation gets
|
|
3646
3987
|
// its own small PORT node INSIDE the container (one per external
|
|
3647
3988
|
// counterpart; incoming left, outgoing right). Children connect to ports
|
|
3648
3989
|
// with short edges that never leave the box \u2014 the real cross-boundary line
|
|
3649
3990
|
// is only revealed on hover, or pinned while the port is selected.
|
|
3650
|
-
function innerLayout(entry) {
|
|
3991
|
+
function innerLayout(entry, deepCtx) {
|
|
3992
|
+
// Deep-flagged subsystems take the recursive path (deepCtx exists only
|
|
3993
|
+
// when Internals is on and the view has deep entries \u2014 see buildElements).
|
|
3994
|
+
if (deepCtx && entry.kind === 'subsystem' && deepCtx.deepByAnchor[anchorNodeId(entry)]) {
|
|
3995
|
+
return deepLayout(entry, deepCtx.ports[anchorNodeId(entry)]);
|
|
3996
|
+
}
|
|
3651
3997
|
var kids = state.internals && entry.hasKids ? childrenOf({ kind: entry.kind, id: entry.id }) : [];
|
|
3652
3998
|
if (!kids.length) return null;
|
|
3653
3999
|
var scope = { kind: entry.kind, id: entry.id };
|
|
@@ -4219,12 +4565,42 @@ var MODEL = __MODEL_JSON__;
|
|
|
4219
4565
|
if (scope.kind === 'types' || scope.kind === 'databases') return buildTypeElements();
|
|
4220
4566
|
var entries = childrenOf(scope);
|
|
4221
4567
|
var eles = [];
|
|
4568
|
+
var deepCtx = buildDeepContext(scope, entries);
|
|
4222
4569
|
var ve = viewEdges(scope, entries);
|
|
4223
4570
|
// Data-coupling overlay: same scoping pipeline, a different edge source.
|
|
4224
4571
|
var vd = state.dataCoupling ? viewEdges(scope, entries, MODEL.dataEdges) : { agg: {}, ghosts: {} };
|
|
4225
4572
|
Object.keys(vd.ghosts).forEach(function (g) { if (!ve.ghosts[g]) ve.ghosts[g] = vd.ghosts[g]; });
|
|
4226
4573
|
var inners = {};
|
|
4227
|
-
entries.forEach(function (e) { inners[anchorNodeId(e)] = innerLayout(e); });
|
|
4574
|
+
entries.forEach(function (e) { inners[anchorNodeId(e)] = innerLayout(e, deepCtx); });
|
|
4575
|
+
|
|
4576
|
+
// Deep-mode recursive tile emission: a nested container becomes a cytoscape
|
|
4577
|
+
// compound parent (no explicit position \u2014 a compound derives its bounds
|
|
4578
|
+
// from its children); leaves and EMPTY containers are plain positioned
|
|
4579
|
+
// nodes. ox/oy = the emitting container's absolute top-left; tile.x/y are
|
|
4580
|
+
// centres relative to it, so offsets accumulate top-down. Search dimming
|
|
4581
|
+
// propagates the TOP entry's dim to the whole subtree.
|
|
4582
|
+
function emitDeepTiles(tiles, parentNodeId, ox, oy, dimCls) {
|
|
4583
|
+
tiles.forEach(function (tile) {
|
|
4584
|
+
var ax = ox + tile.x, ay = oy + tile.y;
|
|
4585
|
+
if (tile.sub) {
|
|
4586
|
+
var nid = SN(tile.kid.id);
|
|
4587
|
+
var selCls = state.selectedKind === 'subsystem' && state.selected === tile.kid.id ? ' sel' : '';
|
|
4588
|
+
var nested = {
|
|
4589
|
+
data: { id: nid, parent: parentNodeId, label: nameOf(tile.kid), w: tile.w, h: tile.h, tw: tile.w - 16 },
|
|
4590
|
+
classes: 'subsysBox' + (tile.sub.tiles.length ? ' drillable' : '') + dimCls + selCls,
|
|
4591
|
+
};
|
|
4592
|
+
if (!tile.sub.tiles.length) nested.position = { x: ax, y: ay };
|
|
4593
|
+
eles.push(nested);
|
|
4594
|
+
emitDeepTiles(tile.sub.tiles, nid, ax - tile.w / 2, ay - tile.h / 2, dimCls);
|
|
4595
|
+
} else {
|
|
4596
|
+
eles.push({
|
|
4597
|
+
data: { id: IN(tile.kid.kind, tile.kid.id), parent: parentNodeId, label: nameOf(tile.kid), w: INNER_W, h: INNER_H, tw: INNER_W - 10 },
|
|
4598
|
+
position: { x: ax, y: ay },
|
|
4599
|
+
classes: 'inner' + dimCls,
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4228
4604
|
|
|
4229
4605
|
// Resolve a port's reveal target(s) in THIS view. Preference order: the
|
|
4230
4606
|
// MATCHING PORT inside the counterpart's container (a port-to-port line
|
|
@@ -4356,17 +4732,25 @@ var MODEL = __MODEL_JSON__;
|
|
|
4356
4732
|
+ (state.showIssues && issuesBySpec[e.id] ? ' hasIssue' : '')
|
|
4357
4733
|
+ (state.selectedKind === e.kind && state.selected === e.id ? ' sel' : '');
|
|
4358
4734
|
if (inner) {
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4735
|
+
var boxNode = { data: { id: aid, label: e.kind === 'subsystem' ? nameOf(e) : nameOf(e), w: p.w, h: p.h, tw: p.w - 16 }, classes: classes };
|
|
4736
|
+
// An EMPTY deep boundary box has no children, so it is NOT a compound
|
|
4737
|
+
// parent \u2014 it needs (and honours) an explicit position and size.
|
|
4738
|
+
if (inner.deep && !inner.tiles.length && !(inner.proxies && inner.proxies.length)) boxNode.position = { x: p.x, y: p.y };
|
|
4739
|
+
eles.push(boxNode);
|
|
4740
|
+
if (inner.deep) {
|
|
4741
|
+
emitDeepTiles(inner.tiles, aid, p.x - p.w / 2, p.y - p.h / 2, dim ? ' dimmed' : '');
|
|
4742
|
+
} else {
|
|
4743
|
+
inner.tiles.forEach(function (tile) {
|
|
4744
|
+
eles.push({
|
|
4745
|
+
data: {
|
|
4746
|
+
id: IN(tile.kid.kind, tile.kid.id), parent: aid,
|
|
4747
|
+
label: nameOf(tile.kid), w: INNER_W, h: INNER_H, tw: INNER_W - 10,
|
|
4748
|
+
},
|
|
4749
|
+
position: { x: p.x - p.w / 2 + tile.x, y: p.y - p.h / 2 + tile.y },
|
|
4750
|
+
classes: 'inner' + (dim ? ' dimmed' : ''),
|
|
4751
|
+
});
|
|
4368
4752
|
});
|
|
4369
|
-
}
|
|
4753
|
+
}
|
|
4370
4754
|
(inner.proxies || []).forEach(function (px) {
|
|
4371
4755
|
eles.push({
|
|
4372
4756
|
data: {
|
|
@@ -4519,9 +4903,28 @@ var MODEL = __MODEL_JSON__;
|
|
|
4519
4903
|
|
|
4520
4904
|
var dimmedAnchors = {};
|
|
4521
4905
|
entries.forEach(function (e) { if (state.query && !matches(e)) dimmedAnchors[anchorNodeId(e)] = true; });
|
|
4906
|
+
|
|
4907
|
+
// Deep mode: ONE direct line per related pair of concrete visible nodes \u2014
|
|
4908
|
+
// leaf tiles at any depth and/or top-level component boxes (deduped by
|
|
4909
|
+
// buildDeepContext). These lines cross nested boundaries on purpose.
|
|
4910
|
+
if (deepCtx) {
|
|
4911
|
+
var ddi = 0;
|
|
4912
|
+
Object.keys(deepCtx.direct).sort().forEach(function (key) {
|
|
4913
|
+
var d = deepCtx.direct[key];
|
|
4914
|
+
var ddim = state.query && (dimmedAnchors[d.aTop] || dimmedAnchors[d.bTop]);
|
|
4915
|
+
eles.push({
|
|
4916
|
+
data: { id: 'dd' + (ddi++), source: d.src, target: d.tgt, lbl: '' },
|
|
4917
|
+
classes: 'inneredge' + (d.cross ? ' cross' : '') + (ddim ? ' dimmed' : ''),
|
|
4918
|
+
});
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4522
4922
|
var i = 0;
|
|
4523
4923
|
Object.keys(ve.agg).forEach(function (key) {
|
|
4524
4924
|
var e = ve.agg[key];
|
|
4925
|
+
// Prefer the leaf lines: drop an aggregated container edge whose
|
|
4926
|
+
// constituent relations were ALL drawn as direct deep lines above.
|
|
4927
|
+
if (deepCtx && deepCtx.directTopCount[key] >= e.n) return;
|
|
4525
4928
|
var bundle = e.n > 1;
|
|
4526
4929
|
var dim = state.query && (dimmedAnchors[e.src] || dimmedAnchors[e.tgt]);
|
|
4527
4930
|
var route = routeData(e.src, e.tgt, key);
|
|
@@ -4780,22 +5183,58 @@ var MODEL = __MODEL_JSON__;
|
|
|
4780
5183
|
}
|
|
4781
5184
|
return path;
|
|
4782
5185
|
}
|
|
5186
|
+
// Crumb compaction (compaction stage 5) is a RENDER MODE, not marker-based
|
|
5187
|
+
// reparenting: renderCrumbs rebuilds #crumbs' innerHTML on every navigation,
|
|
5188
|
+
// so nodes physically moved elsewhere would be destroyed by the next render.
|
|
5189
|
+
// The header-compaction stage toggles crumbsCompact and re-renders; compact
|
|
5190
|
+
// keeps the CURRENT scope visible and folds the ancestors into an ordered
|
|
5191
|
+
// "\\u2026" dropdown (document order, root first) whose entries navigate
|
|
5192
|
+
// exactly like the crumbs they replace.
|
|
5193
|
+
var crumbsCompact = false;
|
|
5194
|
+
var lastCrumbsHtml; // no initializer: the boot render at cy-init time precedes this line
|
|
5195
|
+
// Set by the header-compaction IIFE: crumb re-renders change the header's
|
|
5196
|
+
// CONTENT width without resizing #hdr itself (it is edge-anchored), so the
|
|
5197
|
+
// ResizeObserver never fires for them \u2014 renderCrumbs nudges a reflow here.
|
|
5198
|
+
var headerReflowHook = null;
|
|
5199
|
+
function crumbBtnHtml(p, cur) {
|
|
5200
|
+
return '<button class="crumb' + (cur ? ' cur' : '') + '" data-ck="' + p.kind + '" data-ci="' + (p.id || '') + '">' + p.label + '</button>';
|
|
5201
|
+
}
|
|
4783
5202
|
function renderCrumbs() {
|
|
4784
5203
|
var el = document.getElementById('crumbs');
|
|
4785
5204
|
var path = crumbPath();
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
+
|
|
4790
|
-
|
|
5205
|
+
var html;
|
|
5206
|
+
if (crumbsCompact && path.length > 1) {
|
|
5207
|
+
html = '<span class="dropdown" id="crumbDd">'
|
|
5208
|
+
+ '<button class="crumb crumbmore" id="crumbMoreBtn" title="Show the collapsed ancestor path">\\u2026</button>'
|
|
5209
|
+
+ '<span class="menu" id="crumbMenu">'
|
|
5210
|
+
+ path.slice(0, path.length - 1).map(function (p) {
|
|
5211
|
+
return '<button data-ck="' + p.kind + '" data-ci="' + (p.id || '') + '">' + p.label + '</button>';
|
|
5212
|
+
}).join('')
|
|
5213
|
+
+ '</span></span>'
|
|
5214
|
+
+ '<span class="sep">\\u203A</span>'
|
|
5215
|
+
+ crumbBtnHtml(path[path.length - 1], true);
|
|
5216
|
+
} else {
|
|
5217
|
+
html = path.map(function (p, i) {
|
|
5218
|
+
var cur = i === path.length - 1;
|
|
5219
|
+
return crumbBtnHtml(p, cur) + (cur ? '' : '<span class="sep">\\u203A</span>');
|
|
5220
|
+
}).join('');
|
|
5221
|
+
}
|
|
5222
|
+
// No-op renders keep the already-wired nodes (and an open "\\u2026" menu)
|
|
5223
|
+
// intact \u2014 and don't churn the reflow scheduler while a search query types.
|
|
5224
|
+
if (html === lastCrumbsHtml) return;
|
|
5225
|
+
lastCrumbsHtml = html;
|
|
5226
|
+
el.innerHTML = html;
|
|
4791
5227
|
var btns = el.querySelectorAll('button');
|
|
4792
5228
|
for (var i = 0; i < btns.length; i++) {
|
|
4793
5229
|
(function (b) {
|
|
5230
|
+
if (!b.getAttribute('data-ck')) return; // the "\\u2026" trigger toggles, never navigates
|
|
4794
5231
|
b.addEventListener('click', function () {
|
|
4795
5232
|
navigateTo(b.getAttribute('data-ck'), b.getAttribute('data-ci') || null);
|
|
4796
5233
|
});
|
|
4797
5234
|
})(btns[i]);
|
|
4798
5235
|
}
|
|
5236
|
+
if (crumbsCompact && path.length > 1) wireDropdown('crumbDd', 'crumbMoreBtn');
|
|
5237
|
+
if (headerReflowHook) headerReflowHook();
|
|
4799
5238
|
}
|
|
4800
5239
|
function renderViewHint() {
|
|
4801
5240
|
if (state.view.kind === 'types' || state.view.kind === 'databases') {
|
|
@@ -5062,7 +5501,16 @@ var MODEL = __MODEL_JSON__;
|
|
|
5062
5501
|
return c ? { kind: 'subsystem', id: c.subsystem } : { kind: 'system', id: null };
|
|
5063
5502
|
}
|
|
5064
5503
|
|
|
5065
|
-
|
|
5504
|
+
// While the search input is compacted behind the magnifier icon (compaction
|
|
5505
|
+
// stage 3), a non-empty query must stay discoverable \u2014 mark the icon with an
|
|
5506
|
+
// accent dot. The class is kept in sync on every query edit; the dot is only
|
|
5507
|
+
// ever visible while the compact icon itself is.
|
|
5508
|
+
function updateSearchBadge() {
|
|
5509
|
+
var b = document.getElementById('searchBtn');
|
|
5510
|
+
if (b && b.classList) b.classList[state.query ? 'add' : 'remove']('hasq');
|
|
5511
|
+
}
|
|
5512
|
+
document.getElementById('search').addEventListener('input', function (ev) { state.query = ev.target.value.trim(); updateSearchBadge(); rebuild(false); });
|
|
5513
|
+
updateSearchBadge();
|
|
5066
5514
|
// Sync each View toggle's checkbox from the (possibly persisted) state, then
|
|
5067
5515
|
// persist on change so the choices survive a refresh (see persist()/saved).
|
|
5068
5516
|
document.getElementById('internalsToggle').checked = state.internals;
|
|
@@ -5116,9 +5564,24 @@ var MODEL = __MODEL_JSON__;
|
|
|
5116
5564
|
})(btns[i]);
|
|
5117
5565
|
}
|
|
5118
5566
|
})();
|
|
5567
|
+
// Compaction stage 4 replaces the mode tabs with one dropdown trigger; its
|
|
5568
|
+
// label must follow the CURRENT mode. updateHeaderSegs runs on every rebuild,
|
|
5569
|
+
// so a mode change made while compact re-labels the trigger immediately.
|
|
5570
|
+
function updateModeBtn() {
|
|
5571
|
+
var b = document.getElementById('modeBtn');
|
|
5572
|
+
if (!b) return;
|
|
5573
|
+
var lbl = state.view.kind === 'types' ? 'Types' : state.view.kind === 'databases' ? 'Databases' : 'Components';
|
|
5574
|
+
b.textContent = lbl + ' \\u25BE';
|
|
5575
|
+
}
|
|
5119
5576
|
function updateHeaderSegs() {
|
|
5120
5577
|
var seg = document.getElementById('modeSeg');
|
|
5121
5578
|
var btns = seg.querySelectorAll('button');
|
|
5579
|
+
if (!btns.length) {
|
|
5580
|
+
// Compaction stage 4 moved the real tab buttons into the mode dropdown \u2014
|
|
5581
|
+
// keep driving THEIR active classes there (they move back node-identical).
|
|
5582
|
+
var mm = document.getElementById('modeMenu');
|
|
5583
|
+
if (mm && mm.querySelectorAll) btns = mm.querySelectorAll('button');
|
|
5584
|
+
}
|
|
5122
5585
|
for (var i = 0; i < btns.length; i++) {
|
|
5123
5586
|
var vm = btns[i].getAttribute('data-vm');
|
|
5124
5587
|
var active = vm === 'components'
|
|
@@ -5126,6 +5589,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
5126
5589
|
: vm === state.view.kind;
|
|
5127
5590
|
if (btns[i].classList) btns[i].classList[active ? 'add' : 'remove']('active');
|
|
5128
5591
|
}
|
|
5592
|
+
updateModeBtn();
|
|
5129
5593
|
var td = document.getElementById('typesDetailSeg');
|
|
5130
5594
|
td.style.display = (state.view.kind === 'types' || state.view.kind === 'databases') ? '' : 'none';
|
|
5131
5595
|
var tbs = td.querySelectorAll('button');
|
|
@@ -5189,7 +5653,13 @@ var MODEL = __MODEL_JSON__;
|
|
|
5189
5653
|
var r = btn.getBoundingClientRect();
|
|
5190
5654
|
menu.style.top = (r.bottom + 6) + 'px';
|
|
5191
5655
|
menu.style.left = 'auto';
|
|
5192
|
-
|
|
5656
|
+
// Right-aligned to the trigger, but never pushed off the LEFT edge \u2014 the
|
|
5657
|
+
// compact search / crumb triggers (header compaction) sit on the header's
|
|
5658
|
+
// left side, where a 200px menu right-aligned to a narrow button would clip.
|
|
5659
|
+
var right = Math.max(6, window.innerWidth - r.right);
|
|
5660
|
+
var mw = menu.getBoundingClientRect ? menu.getBoundingClientRect().width : 0;
|
|
5661
|
+
if (mw && window.innerWidth - right - mw < 6) right = Math.max(6, window.innerWidth - mw - 6);
|
|
5662
|
+
menu.style.right = right + 'px';
|
|
5193
5663
|
}
|
|
5194
5664
|
function wireDropdown(ddId, btnId) {
|
|
5195
5665
|
var dd = document.getElementById(ddId);
|
|
@@ -5208,11 +5678,29 @@ var MODEL = __MODEL_JSON__;
|
|
|
5208
5678
|
var ldd = wireDropdown('layoutDd', 'layoutBtn');
|
|
5209
5679
|
var sdd = wireDropdown('settingsDd', 'settingsBtn');
|
|
5210
5680
|
var mdd = wireDropdown('moreDd', 'moreBtn');
|
|
5681
|
+
// Compact stand-ins (header compaction stages 3-4): the search panel and the
|
|
5682
|
+
// mode-tab dropdown are ordinary dropdowns; their triggers stay hidden until
|
|
5683
|
+
// their compaction stage shows them, so wiring them here is inert at full width.
|
|
5684
|
+
var qdd = wireDropdown('searchDd', 'searchBtn');
|
|
5685
|
+
var vdd = wireDropdown('modeDd', 'modeBtn');
|
|
5686
|
+
// Opening the compact search panel focuses the REAL input (stage 3 moves the
|
|
5687
|
+
// node, never clones it, so its input listener keeps driving state.query).
|
|
5688
|
+
// Registered after wireDropdown's toggle, so 'open' reflects the new state.
|
|
5689
|
+
document.getElementById('searchBtn').addEventListener('click', function () {
|
|
5690
|
+
if (String(qdd.className || '').indexOf('open') >= 0) {
|
|
5691
|
+
var inp = document.getElementById('search');
|
|
5692
|
+
if (inp && inp.focus) inp.focus();
|
|
5693
|
+
}
|
|
5694
|
+
});
|
|
5211
5695
|
// Keep the settings panel open while flipping switches (clicks inside it don't
|
|
5212
5696
|
// bubble to the document-level close handler).
|
|
5213
5697
|
(function () {
|
|
5214
5698
|
var m = document.getElementById('settingsMenu');
|
|
5215
5699
|
if (m && m.addEventListener) m.addEventListener('click', function (ev) { if (ev && ev.stopPropagation) ev.stopPropagation(); });
|
|
5700
|
+
// Same for the floating search panel: clicking into the input must not
|
|
5701
|
+
// bubble to the document-level close handler and shut the panel mid-typing.
|
|
5702
|
+
var sm = document.getElementById('searchMenu');
|
|
5703
|
+
if (sm && sm.addEventListener) sm.addEventListener('click', function (ev) { if (ev && ev.stopPropagation) ev.stopPropagation(); });
|
|
5216
5704
|
})();
|
|
5217
5705
|
|
|
5218
5706
|
// Layout picker: choose the auto-layout algorithm. Components use cytoscape's
|
|
@@ -5236,6 +5724,12 @@ var MODEL = __MODEL_JSON__;
|
|
|
5236
5724
|
});
|
|
5237
5725
|
updateLayoutBtn();
|
|
5238
5726
|
|
|
5727
|
+
// The compact crumb dropdown (compaction stage 5) is re-created by every
|
|
5728
|
+
// compact crumb render, so it is looked up per close instead of captured.
|
|
5729
|
+
function closeCrumbDd() {
|
|
5730
|
+
var cdd = document.getElementById('crumbDd');
|
|
5731
|
+
if (cdd && cdd.classList) cdd.classList.remove('open');
|
|
5732
|
+
}
|
|
5239
5733
|
if (document.addEventListener) {
|
|
5240
5734
|
document.addEventListener('click', function () {
|
|
5241
5735
|
if (dd.classList) dd.classList.remove('open');
|
|
@@ -5243,6 +5737,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
5243
5737
|
if (ldd.classList) ldd.classList.remove('open');
|
|
5244
5738
|
if (sdd.classList) sdd.classList.remove('open');
|
|
5245
5739
|
if (mdd && mdd.classList) mdd.classList.remove('open');
|
|
5740
|
+
if (qdd && qdd.classList) qdd.classList.remove('open');
|
|
5741
|
+
if (vdd && vdd.classList) vdd.classList.remove('open');
|
|
5742
|
+
closeCrumbDd();
|
|
5246
5743
|
});
|
|
5247
5744
|
document.addEventListener('keydown', function (ev) {
|
|
5248
5745
|
if (ev.key === 'Escape') {
|
|
@@ -5250,16 +5747,27 @@ var MODEL = __MODEL_JSON__;
|
|
|
5250
5747
|
if (modal.classList && String(modal.className).indexOf('open') >= 0) { closeFlow(); return; }
|
|
5251
5748
|
setPresentation(false);
|
|
5252
5749
|
if (dd.classList) dd.classList.remove('open');
|
|
5750
|
+
if (qdd && qdd.classList) qdd.classList.remove('open');
|
|
5751
|
+
if (vdd && vdd.classList) vdd.classList.remove('open');
|
|
5752
|
+
closeCrumbDd();
|
|
5253
5753
|
}
|
|
5254
5754
|
});
|
|
5255
5755
|
}
|
|
5256
5756
|
|
|
5257
|
-
// \u2500\u2500 Responsive header
|
|
5258
|
-
// When the floating header no longer fits its controls,
|
|
5259
|
-
//
|
|
5260
|
-
//
|
|
5261
|
-
//
|
|
5262
|
-
//
|
|
5757
|
+
// \u2500\u2500 Responsive header compaction \u2192 ordered stages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5758
|
+
// When the floating header no longer fits its controls, standard reusable
|
|
5759
|
+
// COMPACTION BEHAVIORS apply progressively \u2014 each stage only while the row
|
|
5760
|
+
// still overflows \u2014 and restore in REVERSE order when space returns:
|
|
5761
|
+
// 1. trailing buttons fold into the "\u22EF" menu (least-used first, one by one)
|
|
5762
|
+
// 2. the View dropdown folds in after them
|
|
5763
|
+
// 3. the search input compacts to a \u{1F50D} icon + floating panel
|
|
5764
|
+
// 4. the mode tabs compact to one current-mode dropdown
|
|
5765
|
+
// 5. ancestor crumbs compact into an ordered "\u2026" dropdown
|
|
5766
|
+
// Stages 1-2 move whole items (listeners survive reparenting) with a hidden
|
|
5767
|
+
// placeholder pinning each item's original spot for restore; stage 5 is a
|
|
5768
|
+
// render mode (renderCrumbs rebuilds its innerHTML, so reparenting would not
|
|
5769
|
+
// survive navigation). Nothing here is persisted \u2014 compaction is purely
|
|
5770
|
+
// responsive to the available width.
|
|
5263
5771
|
(function () {
|
|
5264
5772
|
if (typeof window === 'undefined') return;
|
|
5265
5773
|
var hdr = document.getElementById('hdr');
|
|
@@ -5287,7 +5795,122 @@ var MODEL = __MODEL_JSON__;
|
|
|
5287
5795
|
}
|
|
5288
5796
|
return markers[id];
|
|
5289
5797
|
}
|
|
5290
|
-
|
|
5798
|
+
// Fold stage (the classic behavior): move items into the "\u22EF" menu ONE per
|
|
5799
|
+
// apply() call \u2014 the reflow loop keeps a stage active until it reports no
|
|
5800
|
+
// further progress, preserving the original per-button granularity.
|
|
5801
|
+
function foldStage(ids) {
|
|
5802
|
+
var folded = [];
|
|
5803
|
+
return {
|
|
5804
|
+
apply: function () {
|
|
5805
|
+
while (folded.length < ids.length) {
|
|
5806
|
+
var id = ids[folded.length];
|
|
5807
|
+
var el = movableFor(id);
|
|
5808
|
+
if (!el || el === moreDd || el.parentNode === moreMenu) { folded.push({ el: null, marker: null }); continue; }
|
|
5809
|
+
var m = markerFor(id, el);
|
|
5810
|
+
// A dropdown moved while open would strand its fixed-positioned menu.
|
|
5811
|
+
if (el.classList) el.classList.remove('open');
|
|
5812
|
+
moreDd.style.display = '';
|
|
5813
|
+
moreMenu.appendChild(el);
|
|
5814
|
+
folded.push({ el: el, marker: m });
|
|
5815
|
+
return true;
|
|
5816
|
+
}
|
|
5817
|
+
return false;
|
|
5818
|
+
},
|
|
5819
|
+
restore: function () {
|
|
5820
|
+
for (var i = folded.length - 1; i >= 0; i--) {
|
|
5821
|
+
var it = folded[i];
|
|
5822
|
+
if (it.el && it.marker && it.marker.parentNode) it.marker.parentNode.insertBefore(it.el, it.marker);
|
|
5823
|
+
}
|
|
5824
|
+
folded = [];
|
|
5825
|
+
},
|
|
5826
|
+
};
|
|
5827
|
+
}
|
|
5828
|
+
// Stage 3: the search input compacts behind a \u{1F50D} icon; the REAL input node
|
|
5829
|
+
// MOVES into the floating panel (fixed-positioned by the same helper as
|
|
5830
|
+
// every dropdown menu), so its input listener keeps driving state.query.
|
|
5831
|
+
function searchStage() {
|
|
5832
|
+
var on = false;
|
|
5833
|
+
return {
|
|
5834
|
+
apply: function () {
|
|
5835
|
+
if (on) return false;
|
|
5836
|
+
var inp = document.getElementById('search');
|
|
5837
|
+
var ddw = document.getElementById('searchDd');
|
|
5838
|
+
var menu = document.getElementById('searchMenu');
|
|
5839
|
+
if (!inp || !ddw || !menu) return false;
|
|
5840
|
+
menu.appendChild(inp);
|
|
5841
|
+
ddw.style.display = '';
|
|
5842
|
+
on = true;
|
|
5843
|
+
return true;
|
|
5844
|
+
},
|
|
5845
|
+
restore: function () {
|
|
5846
|
+
if (!on) return;
|
|
5847
|
+
on = false;
|
|
5848
|
+
var inp = document.getElementById('search');
|
|
5849
|
+
var ddw = document.getElementById('searchDd');
|
|
5850
|
+
if (inp && ddw && ddw.parentNode) ddw.parentNode.insertBefore(inp, ddw);
|
|
5851
|
+
if (ddw) { ddw.style.display = 'none'; if (ddw.classList) ddw.classList.remove('open'); }
|
|
5852
|
+
},
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
// Stage 4: the mode tabs collapse into ONE dropdown labelled with the
|
|
5856
|
+
// current mode. The REAL tab buttons move into its menu (listeners and
|
|
5857
|
+
// active styling survive); updateHeaderSegs keeps the trigger label in
|
|
5858
|
+
// sync when the mode changes while compact.
|
|
5859
|
+
function modeStage() {
|
|
5860
|
+
var moved = [];
|
|
5861
|
+
return {
|
|
5862
|
+
apply: function () {
|
|
5863
|
+
if (moved.length) return false;
|
|
5864
|
+
var seg = document.getElementById('modeSeg');
|
|
5865
|
+
var ddw = document.getElementById('modeDd');
|
|
5866
|
+
var menu = document.getElementById('modeMenu');
|
|
5867
|
+
if (!seg || !ddw || !menu) return false;
|
|
5868
|
+
var btns = seg.querySelectorAll('button');
|
|
5869
|
+
if (!btns.length) return false;
|
|
5870
|
+
for (var i = 0; i < btns.length; i++) moved.push(btns[i]);
|
|
5871
|
+
for (var j = 0; j < moved.length; j++) menu.appendChild(moved[j]);
|
|
5872
|
+
seg.style.display = 'none';
|
|
5873
|
+
ddw.style.display = '';
|
|
5874
|
+
updateModeBtn();
|
|
5875
|
+
return true;
|
|
5876
|
+
},
|
|
5877
|
+
restore: function () {
|
|
5878
|
+
if (!moved.length) return;
|
|
5879
|
+
var seg = document.getElementById('modeSeg');
|
|
5880
|
+
var ddw = document.getElementById('modeDd');
|
|
5881
|
+
for (var i = 0; i < moved.length; i++) seg.appendChild(moved[i]);
|
|
5882
|
+
moved = [];
|
|
5883
|
+
seg.style.display = '';
|
|
5884
|
+
if (ddw) { ddw.style.display = 'none'; if (ddw.classList) ddw.classList.remove('open'); }
|
|
5885
|
+
},
|
|
5886
|
+
};
|
|
5887
|
+
}
|
|
5888
|
+
// Stage 5: crumb compaction is a render-mode toggle consulted by
|
|
5889
|
+
// renderCrumbs itself \u2014 see crumbsCompact there. Never reparenting.
|
|
5890
|
+
function crumbStage() {
|
|
5891
|
+
return {
|
|
5892
|
+
apply: function () {
|
|
5893
|
+
if (crumbsCompact) return false;
|
|
5894
|
+
crumbsCompact = true;
|
|
5895
|
+
renderCrumbs();
|
|
5896
|
+
return true;
|
|
5897
|
+
},
|
|
5898
|
+
restore: function () {
|
|
5899
|
+
if (!crumbsCompact) return;
|
|
5900
|
+
crumbsCompact = false;
|
|
5901
|
+
renderCrumbs();
|
|
5902
|
+
},
|
|
5903
|
+
};
|
|
5904
|
+
}
|
|
5905
|
+
// Ordered compaction stages: applied first-to-last only while the header
|
|
5906
|
+
// overflows, restored last-to-first when space returns.
|
|
5907
|
+
var STAGES = [
|
|
5908
|
+
foldStage(COLLAPSE), // 1: trailing buttons \u2192 "\u22EF" menu
|
|
5909
|
+
foldStage(['settingsBtn']), // 2: the View dropdown folds in too
|
|
5910
|
+
searchStage(), // 3: search input \u2192 \u{1F50D} + floating panel
|
|
5911
|
+
modeStage(), // 4: mode tabs \u2192 current-mode dropdown
|
|
5912
|
+
crumbStage(), // 5: ancestor crumbs \u2192 "\u2026" dropdown
|
|
5913
|
+
];
|
|
5291
5914
|
// Signed fit measure in px: positive = overflowing, negative = headroom.
|
|
5292
5915
|
// The header is a flex row whose ONLY flex:1 child is the .spacer, so the
|
|
5293
5916
|
// spacer's rendered width IS the free space -- it grows to absorb all slack
|
|
@@ -5303,33 +5926,54 @@ var MODEL = __MODEL_JSON__;
|
|
|
5303
5926
|
var slack = spacer ? spacer.getBoundingClientRect().width : 0;
|
|
5304
5927
|
return (hdr.scrollWidth - hdr.clientWidth) - slack;
|
|
5305
5928
|
}
|
|
5929
|
+
var inReflow = false;
|
|
5306
5930
|
function reflow() {
|
|
5307
5931
|
// Not laid out (hidden tab, non-browser DOM) \u2014 measuring would misfire.
|
|
5308
5932
|
var box = hdr.getBoundingClientRect();
|
|
5309
5933
|
if (!box || box.width <= 0) return;
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
var
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5934
|
+
inReflow = true;
|
|
5935
|
+
try {
|
|
5936
|
+
// The floating search panel must survive a reflow cycle: restore-all
|
|
5937
|
+
// would close it (and reparenting blurs the input), so capture its
|
|
5938
|
+
// open/focus state up front and reinstate it after the stage walk.
|
|
5939
|
+
var ddw = document.getElementById('searchDd');
|
|
5940
|
+
var inp = document.getElementById('search');
|
|
5941
|
+
var searchOpen = !!(ddw && String(ddw.className || '').indexOf('open') >= 0);
|
|
5942
|
+
var searchFocus = false;
|
|
5943
|
+
try {
|
|
5944
|
+
var ae = (typeof ROOT !== 'undefined' && ROOT ? ROOT : document).activeElement;
|
|
5945
|
+
searchFocus = !!(ae && inp && ae === inp);
|
|
5946
|
+
} catch (e) { /* stubbed DOM */ }
|
|
5947
|
+
// Restore every stage in REVERSE order, then re-apply progressively
|
|
5948
|
+
// while the row still overflows (idempotent).
|
|
5949
|
+
for (var i = STAGES.length - 1; i >= 0; i--) STAGES[i].restore();
|
|
5950
|
+
moreDd.style.display = 'none';
|
|
5951
|
+
hdr.scrollLeft = 0;
|
|
5952
|
+
// Demand a few px of headroom, not a bare fit \u2014 the marginal-fit widths
|
|
5953
|
+
// are exactly where the phantom scrollbar appeared. Every apply()
|
|
5954
|
+
// changes the very widths being measured, but restore-all + a strictly
|
|
5955
|
+
// forward stage walk make the outcome a pure function of the current
|
|
5956
|
+
// width, and reflow never reschedules itself (renderCrumbs' nudge is
|
|
5957
|
+
// suppressed via inReflow, and #hdr's own box never changes here), so
|
|
5958
|
+
// boundary widths settle in ONE pass instead of oscillating.
|
|
5959
|
+
var si = 0;
|
|
5960
|
+
var guard = 0;
|
|
5961
|
+
var bound = COLLAPSE.length + STAGES.length + 8;
|
|
5962
|
+
while (overflowPx() > -8 && si < STAGES.length && guard < bound) {
|
|
5963
|
+
guard++;
|
|
5964
|
+
if (!STAGES[si].apply()) si++;
|
|
5965
|
+
}
|
|
5966
|
+
if (searchOpen || searchFocus) {
|
|
5967
|
+
var compactNow = ddw && ddw.style && ddw.style.display !== 'none';
|
|
5968
|
+
if (compactNow && searchOpen) {
|
|
5969
|
+
if (ddw.classList) ddw.classList.add('open');
|
|
5970
|
+
positionDropdownMenu(ddw, document.getElementById('searchBtn'));
|
|
5971
|
+
}
|
|
5972
|
+
if (searchFocus && inp && inp.focus) inp.focus();
|
|
5973
|
+
}
|
|
5974
|
+
} finally {
|
|
5975
|
+
inReflow = false;
|
|
5976
|
+
}
|
|
5333
5977
|
}
|
|
5334
5978
|
var raf = null;
|
|
5335
5979
|
var defer = window.requestAnimationFrame
|
|
@@ -5339,6 +5983,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
5339
5983
|
if (raf !== null) return;
|
|
5340
5984
|
raf = defer(function () { raf = null; reflow(); });
|
|
5341
5985
|
}
|
|
5986
|
+
// Crumb re-renders change the header's content width without resizing #hdr
|
|
5987
|
+
// itself \u2014 renderCrumbs nudges a reflow through this hook (no-op mid-reflow).
|
|
5988
|
+
headerReflowHook = function () { if (!inReflow) schedule(); };
|
|
5342
5989
|
if (typeof ResizeObserver !== 'undefined') {
|
|
5343
5990
|
new ResizeObserver(schedule).observe(hdr);
|
|
5344
5991
|
} else if (window.addEventListener) {
|
|
@@ -6869,47 +7516,6 @@ var init_filenames = __esm({
|
|
|
6869
7516
|
}
|
|
6870
7517
|
});
|
|
6871
7518
|
|
|
6872
|
-
// src/core/statehash.ts
|
|
6873
|
-
function computeStateId() {
|
|
6874
|
-
const tree = {
|
|
6875
|
-
system: loadSystemSpec(),
|
|
6876
|
-
subsystems: loadSubsystemSpecs(),
|
|
6877
|
-
components: loadComponentSpecs(),
|
|
6878
|
-
interfaces: loadInterfaceSpecs(),
|
|
6879
|
-
implementations: loadImplementationSpecs(),
|
|
6880
|
-
types: loadTypeSpecs()
|
|
6881
|
-
};
|
|
6882
|
-
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
6883
|
-
return { algorithm: "sha256", digest };
|
|
6884
|
-
}
|
|
6885
|
-
function stateIdEquals(a, b) {
|
|
6886
|
-
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
6887
|
-
}
|
|
6888
|
-
function canonicalize(value) {
|
|
6889
|
-
return JSON.stringify(sortKeys(value));
|
|
6890
|
-
}
|
|
6891
|
-
function sortKeys(v) {
|
|
6892
|
-
if (Array.isArray(v)) return v.map(sortKeys);
|
|
6893
|
-
if (v && typeof v === "object") {
|
|
6894
|
-
const src = v;
|
|
6895
|
-
const out = {};
|
|
6896
|
-
for (const k of Object.keys(src).sort()) {
|
|
6897
|
-
if (k === "createdAt" || k === "updatedAt") continue;
|
|
6898
|
-
out[k] = sortKeys(src[k]);
|
|
6899
|
-
}
|
|
6900
|
-
return out;
|
|
6901
|
-
}
|
|
6902
|
-
return v;
|
|
6903
|
-
}
|
|
6904
|
-
var crypto;
|
|
6905
|
-
var init_statehash = __esm({
|
|
6906
|
-
"src/core/statehash.ts"() {
|
|
6907
|
-
"use strict";
|
|
6908
|
-
crypto = __toESM(require("crypto"));
|
|
6909
|
-
init_specs2();
|
|
6910
|
-
}
|
|
6911
|
-
});
|
|
6912
|
-
|
|
6913
7519
|
// src/core/openapi.ts
|
|
6914
7520
|
function schemaFor(typeRef, closureIds) {
|
|
6915
7521
|
const trimmed = typeRef.trim().replace(/^promise\s*<(.+)>$/i, "$1").trim();
|
|
@@ -7363,6 +7969,57 @@ function projectOwnSurface(maxAudience) {
|
|
|
7363
7969
|
function projectChildSurface() {
|
|
7364
7970
|
return projectOwnSurface("project");
|
|
7365
7971
|
}
|
|
7972
|
+
function localName(id) {
|
|
7973
|
+
return id.split("::").pop();
|
|
7974
|
+
}
|
|
7975
|
+
function projectSubsystemSurface(subsystemId) {
|
|
7976
|
+
const system = loadSystemSpec();
|
|
7977
|
+
if (!system) {
|
|
7978
|
+
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
7979
|
+
}
|
|
7980
|
+
const subsystems = loadSubsystemSpecs();
|
|
7981
|
+
const target = subsystems.find((s) => s.id === subsystemId);
|
|
7982
|
+
if (!target) {
|
|
7983
|
+
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
7984
|
+
}
|
|
7985
|
+
const components = loadComponentSpecs();
|
|
7986
|
+
const interfaces = loadInterfaceSpecs();
|
|
7987
|
+
const types = loadTypeSpecs();
|
|
7988
|
+
const entries = [];
|
|
7989
|
+
for (const pub of target.publicInterfaces ?? []) {
|
|
7990
|
+
if (!pub.component) continue;
|
|
7991
|
+
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
7992
|
+
if (!comp) continue;
|
|
7993
|
+
if (comp.componentType !== "Portal") continue;
|
|
7994
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
7995
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
7996
|
+
entries.push({
|
|
7997
|
+
id: localName(pub.interface ?? comp.id),
|
|
7998
|
+
name: comp.name,
|
|
7999
|
+
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
8000
|
+
audience: "project",
|
|
8001
|
+
type: pub.type ?? "Custom",
|
|
8002
|
+
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
8003
|
+
// refs by their final segment.
|
|
8004
|
+
component: localName(comp.id),
|
|
8005
|
+
methods,
|
|
8006
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
8007
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
8008
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
8009
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
8010
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
8011
|
+
details: pub.details ?? ""
|
|
8012
|
+
});
|
|
8013
|
+
}
|
|
8014
|
+
return SurfaceSnapshotSchema.parse({
|
|
8015
|
+
projectName: `${system.name}::${subsystemId}`,
|
|
8016
|
+
origin: "generated",
|
|
8017
|
+
stateId: stateIdString(),
|
|
8018
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8019
|
+
interfaces: entries,
|
|
8020
|
+
types: computeTypeClosure(entries, types)
|
|
8021
|
+
});
|
|
8022
|
+
}
|
|
7366
8023
|
function listSnapshots(rootDir = getProjectRoot()) {
|
|
7367
8024
|
const dir = surfacesDir(rootDir);
|
|
7368
8025
|
if (!fs9.existsSync(dir)) return [];
|
|
@@ -7379,10 +8036,13 @@ function listSnapshots(rootDir = getProjectRoot()) {
|
|
|
7379
8036
|
function getSnapshot(projectName, rootDir = getProjectRoot()) {
|
|
7380
8037
|
return listSnapshots(rootDir).find((s) => s.projectName === projectName) ?? null;
|
|
7381
8038
|
}
|
|
8039
|
+
function snapshotFilename(projectName) {
|
|
8040
|
+
return `${safeFilenamePart(projectName)}.yaml`;
|
|
8041
|
+
}
|
|
7382
8042
|
function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
7383
8043
|
const dir = surfacesDir(rootDir);
|
|
7384
8044
|
fs9.mkdirSync(dir, { recursive: true });
|
|
7385
|
-
const p = path10.join(dir,
|
|
8045
|
+
const p = path10.join(dir, snapshotFilename(snapshot.projectName));
|
|
7386
8046
|
writeYamlFile(p, SurfaceSnapshotSchema.parse(snapshot));
|
|
7387
8047
|
return p;
|
|
7388
8048
|
}
|
|
@@ -7455,17 +8115,54 @@ function importSurface(sourcePath, origin) {
|
|
|
7455
8115
|
return snapshot;
|
|
7456
8116
|
}
|
|
7457
8117
|
function generateChildSnapshots(rootDir = getProjectRoot()) {
|
|
7458
|
-
const
|
|
8118
|
+
const topLevel = loadSubsystemSpecs().filter((s) => !s.id.includes("::"));
|
|
8119
|
+
const children = topLevel.filter((s) => s.projectPath);
|
|
7459
8120
|
if (!children.length) return [];
|
|
7460
|
-
const
|
|
8121
|
+
const familySnapshot = projectChildSurface();
|
|
8122
|
+
const siblingSnapshots = /* @__PURE__ */ new Map();
|
|
8123
|
+
const siblingSurface = (subsystemId) => {
|
|
8124
|
+
let snap = siblingSnapshots.get(subsystemId);
|
|
8125
|
+
if (!snap) {
|
|
8126
|
+
snap = projectSubsystemSurface(subsystemId);
|
|
8127
|
+
siblingSnapshots.set(subsystemId, snap);
|
|
8128
|
+
}
|
|
8129
|
+
return snap;
|
|
8130
|
+
};
|
|
7461
8131
|
const written = [];
|
|
7462
8132
|
for (const child of children) {
|
|
7463
8133
|
const childDir = path10.resolve(rootDir, child.projectPath);
|
|
7464
8134
|
if (!fs9.existsSync(childDir)) continue;
|
|
7465
|
-
written.push(saveSnapshot(
|
|
8135
|
+
written.push(saveSnapshot(familySnapshot, childDir));
|
|
8136
|
+
for (const sibling of topLevel) {
|
|
8137
|
+
if (sibling.id === child.id) continue;
|
|
8138
|
+
written.push(saveSnapshot(siblingSurface(sibling.id), childDir));
|
|
8139
|
+
}
|
|
7466
8140
|
}
|
|
7467
8141
|
return written;
|
|
7468
8142
|
}
|
|
8143
|
+
function computeParentStateId(parentRoot) {
|
|
8144
|
+
return computeStateIdAt(parentRoot);
|
|
8145
|
+
}
|
|
8146
|
+
function listExternalInterfaces() {
|
|
8147
|
+
const snapshots = listSnapshots();
|
|
8148
|
+
const chainingParent = resolveChainingParent();
|
|
8149
|
+
const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
|
|
8150
|
+
return snapshots.map((snapshot) => {
|
|
8151
|
+
const generated = snapshot.origin === "generated";
|
|
8152
|
+
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
8153
|
+
const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
|
|
8154
|
+
return {
|
|
8155
|
+
projectName: snapshot.projectName,
|
|
8156
|
+
origin: snapshot.origin,
|
|
8157
|
+
sourceKind,
|
|
8158
|
+
generatedAt: snapshot.generatedAt,
|
|
8159
|
+
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
8160
|
+
...snapshot.version ? { version: snapshot.version } : {},
|
|
8161
|
+
freshness,
|
|
8162
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
8163
|
+
};
|
|
8164
|
+
});
|
|
8165
|
+
}
|
|
7469
8166
|
function surfaceContentKey(snapshot) {
|
|
7470
8167
|
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
7471
8168
|
return JSON.stringify(content);
|
|
@@ -7674,7 +8371,8 @@ var init_contracts = __esm({
|
|
|
7674
8371
|
"SURFACE_REF_NOT_EXPOSED",
|
|
7675
8372
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
7676
8373
|
impl.id,
|
|
7677
|
-
isDraftCtx
|
|
8374
|
+
isDraftCtx,
|
|
8375
|
+
true
|
|
7678
8376
|
);
|
|
7679
8377
|
}
|
|
7680
8378
|
continue;
|
|
@@ -7731,7 +8429,8 @@ var init_contracts = __esm({
|
|
|
7731
8429
|
"SURFACE_REF_NOT_EXPOSED",
|
|
7732
8430
|
`Method "${implMethod.name}" in implementation "${impl.id}" calls "${step.targetMethod}" on cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
7733
8431
|
impl.id,
|
|
7734
|
-
isDraftCtx
|
|
8432
|
+
isDraftCtx,
|
|
8433
|
+
true
|
|
7735
8434
|
);
|
|
7736
8435
|
} else if (step.assertsGuarantees) {
|
|
7737
8436
|
const declared = new Set(surfaceMethod.guarantees ?? []);
|
|
@@ -7742,7 +8441,8 @@ var init_contracts = __esm({
|
|
|
7742
8441
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
7743
8442
|
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
|
|
7744
8443
|
impl.id,
|
|
7745
|
-
isDraftCtx
|
|
8444
|
+
isDraftCtx,
|
|
8445
|
+
true
|
|
7746
8446
|
);
|
|
7747
8447
|
}
|
|
7748
8448
|
}
|
|
@@ -9230,7 +9930,8 @@ var init_stereotype_deps = __esm({
|
|
|
9230
9930
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
9231
9931
|
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
9232
9932
|
comp.id,
|
|
9233
|
-
isDraftCtx
|
|
9933
|
+
isDraftCtx,
|
|
9934
|
+
true
|
|
9234
9935
|
);
|
|
9235
9936
|
}
|
|
9236
9937
|
continue;
|
|
@@ -11093,11 +11794,11 @@ var init_narrative_antipatterns = __esm({
|
|
|
11093
11794
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
11094
11795
|
if (memberEdges.length === 0) continue;
|
|
11095
11796
|
const anchor = [...memberEdges].sort((a, b) => a.fromKey.localeCompare(b.fromKey))[0];
|
|
11096
|
-
const
|
|
11797
|
+
const path61 = [...keys].sort().join(" \u2192 ");
|
|
11097
11798
|
ctx.addIssue(
|
|
11098
11799
|
"warning",
|
|
11099
11800
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
11100
|
-
`Call cycle with no guard: ${
|
|
11801
|
+
`Call cycle with no guard: ${path61} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
|
|
11101
11802
|
anchor.impl.id,
|
|
11102
11803
|
memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
|
|
11103
11804
|
);
|
|
@@ -12484,9 +13185,15 @@ function buildRuleContext(opts) {
|
|
|
12484
13185
|
...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
|
|
12485
13186
|
// Declarative assertions bring their own namespaced codes — lint.allow
|
|
12486
13187
|
// and severity overrides treat them exactly like builtins.
|
|
12487
|
-
...extensions.assertions.map((a) => a.fullCode)
|
|
13188
|
+
...extensions.assertions.map((a) => a.fullCode),
|
|
13189
|
+
// Entry-point emitted codes: validateSddTree's chained-subproject pass
|
|
13190
|
+
// raises these AFTER the rule run (it post-processes the aggregated issue
|
|
13191
|
+
// list), so no registered rule declares them — but lint.allow validation
|
|
13192
|
+
// must still recognize them as real codes.
|
|
13193
|
+
"CHAINED_SUBPROJECT_CONTEXT",
|
|
13194
|
+
"UNVERIFIED_EXTERNAL_REF"
|
|
12488
13195
|
]);
|
|
12489
|
-
const addIssue = (defaultSeverity, code, message, specId, isDraftContext) => {
|
|
13196
|
+
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved) => {
|
|
12490
13197
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
12491
13198
|
return;
|
|
12492
13199
|
}
|
|
@@ -12504,7 +13211,14 @@ function buildRuleContext(opts) {
|
|
|
12504
13211
|
if (severity === "warning") return;
|
|
12505
13212
|
}
|
|
12506
13213
|
}
|
|
12507
|
-
issues.push({
|
|
13214
|
+
issues.push({
|
|
13215
|
+
severity,
|
|
13216
|
+
code,
|
|
13217
|
+
message,
|
|
13218
|
+
specId,
|
|
13219
|
+
...isDraftContext ? { draftContext: true } : {},
|
|
13220
|
+
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
13221
|
+
});
|
|
12508
13222
|
};
|
|
12509
13223
|
return {
|
|
12510
13224
|
system,
|
|
@@ -12929,24 +13643,54 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
12929
13643
|
for (const rule of ruleSequence()) {
|
|
12930
13644
|
rule.check(ctx);
|
|
12931
13645
|
}
|
|
12932
|
-
const hasCrossTreeSuspects = issues.some(
|
|
13646
|
+
const hasCrossTreeSuspects = issues.some(
|
|
13647
|
+
(i) => SUBPROJECT_REFERENCE_CODES.has(i.code) || SUBPROJECT_CONFORMANCE_CODES.has(i.code)
|
|
13648
|
+
);
|
|
12933
13649
|
const chainingParent = hasCrossTreeSuspects ? findChainingParent(getProjectRoot()) : null;
|
|
12934
13650
|
if (chainingParent) {
|
|
13651
|
+
let unverified = 0;
|
|
12935
13652
|
let downgraded = 0;
|
|
12936
|
-
for (
|
|
12937
|
-
|
|
12938
|
-
if (iss.
|
|
12939
|
-
|
|
12940
|
-
|
|
13653
|
+
for (let at = 0; at < issues.length; at++) {
|
|
13654
|
+
const iss = issues[at];
|
|
13655
|
+
if (SUBPROJECT_REFERENCE_CODES.has(iss.code) && !iss.surfaceResolved) {
|
|
13656
|
+
issues[at] = {
|
|
13657
|
+
severity: "warning",
|
|
13658
|
+
code: "UNVERIFIED_EXTERNAL_REF",
|
|
13659
|
+
crossTreeContext: true,
|
|
13660
|
+
// --ci waives it (parent root is authoritative)
|
|
13661
|
+
specId: iss.specId,
|
|
13662
|
+
...iss.agentId ? { agentId: iss.agentId } : {},
|
|
13663
|
+
...iss.draftContext ? { draftContext: true } : {},
|
|
13664
|
+
message: `Unverified external reference (${iss.code}): ${iss.message} No vendored surface snapshot covers this reference, so it cannot be verified from this chained subproject standalone \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect what this project can consume via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13665
|
+
};
|
|
13666
|
+
unverified++;
|
|
13667
|
+
continue;
|
|
13668
|
+
}
|
|
13669
|
+
if (SUBPROJECT_CONFORMANCE_CODES.has(iss.code)) {
|
|
13670
|
+
if (iss.severity === "error") {
|
|
13671
|
+
iss.severity = "warning";
|
|
13672
|
+
downgraded++;
|
|
13673
|
+
}
|
|
13674
|
+
iss.crossTreeContext = true;
|
|
12941
13675
|
}
|
|
12942
|
-
iss.crossTreeContext = true;
|
|
12943
13676
|
}
|
|
12944
|
-
if (downgraded > 0) {
|
|
13677
|
+
if (unverified > 0 || downgraded > 0) {
|
|
13678
|
+
const notes = [];
|
|
13679
|
+
if (unverified > 0) {
|
|
13680
|
+
notes.push(
|
|
13681
|
+
`${unverified} cross-tree reference(s) have no vendored surface snapshot covering them and were reported as UNVERIFIED_EXTERNAL_REF warnings \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13682
|
+
);
|
|
13683
|
+
}
|
|
13684
|
+
if (downgraded > 0) {
|
|
13685
|
+
notes.push(
|
|
13686
|
+
`${downgraded} code\u2194spec conformance finding(s) (parent-root-relative source paths) were downgraded to warnings.`
|
|
13687
|
+
);
|
|
13688
|
+
}
|
|
12945
13689
|
issues.unshift({
|
|
12946
13690
|
severity: "warning",
|
|
12947
13691
|
code: "CHAINED_SUBPROJECT_CONTEXT",
|
|
12948
13692
|
crossTreeContext: true,
|
|
12949
|
-
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${
|
|
13693
|
+
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${notes.join(" ")} Full cross-tree verification runs from the parent root.`
|
|
12950
13694
|
});
|
|
12951
13695
|
}
|
|
12952
13696
|
}
|
|
@@ -12963,7 +13707,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
12963
13707
|
function validateAsComplete(options) {
|
|
12964
13708
|
return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
|
|
12965
13709
|
}
|
|
12966
|
-
var
|
|
13710
|
+
var SUBPROJECT_REFERENCE_CODES, SUBPROJECT_CONFORMANCE_CODES;
|
|
12967
13711
|
var init_validation = __esm({
|
|
12968
13712
|
"src/core/validation.ts"() {
|
|
12969
13713
|
"use strict";
|
|
@@ -12976,8 +13720,7 @@ var init_validation = __esm({
|
|
|
12976
13720
|
init_source_analysis();
|
|
12977
13721
|
init_specs2();
|
|
12978
13722
|
init_fs();
|
|
12979
|
-
|
|
12980
|
-
// reference resolution
|
|
13723
|
+
SUBPROJECT_REFERENCE_CODES = /* @__PURE__ */ new Set([
|
|
12981
13724
|
"UNDEFINED_TYPE_REFERENCE",
|
|
12982
13725
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
12983
13726
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
@@ -12985,8 +13728,9 @@ var init_validation = __esm({
|
|
|
12985
13728
|
"UNDECLARED_DEPENDENCY_CALL",
|
|
12986
13729
|
"INVALID_TRUSTED_LINK",
|
|
12987
13730
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
12988
|
-
"CROSS_TREE_REF_UNRESOLVED"
|
|
12989
|
-
|
|
13731
|
+
"CROSS_TREE_REF_UNRESOLVED"
|
|
13732
|
+
]);
|
|
13733
|
+
SUBPROJECT_CONFORMANCE_CODES = /* @__PURE__ */ new Set([
|
|
12990
13734
|
"MISSING_SOURCE_FILE",
|
|
12991
13735
|
"SOURCE_PATH_ESCAPES_ROOT",
|
|
12992
13736
|
"MISSING_SOURCE_PATH",
|
|
@@ -13528,6 +14272,7 @@ __export(specs_exports, {
|
|
|
13528
14272
|
buildProjectGraph: () => buildProjectGraph,
|
|
13529
14273
|
clearLoaderIssues: () => clearLoaderIssues,
|
|
13530
14274
|
collectPromotableSpecs: () => collectPromotableSpecs,
|
|
14275
|
+
computeStateIdAt: () => computeStateIdAt,
|
|
13531
14276
|
deleteComponentSpec: () => deleteComponentSpec,
|
|
13532
14277
|
deleteGroupSpec: () => deleteGroupSpec,
|
|
13533
14278
|
deleteImplementationSpec: () => deleteImplementationSpec,
|
|
@@ -13560,6 +14305,7 @@ __export(specs_exports, {
|
|
|
13560
14305
|
loadTypeSpec: () => loadTypeSpec,
|
|
13561
14306
|
loadTypeSpecs: () => loadTypeSpecs,
|
|
13562
14307
|
normalizeComponentLayout: () => normalizeComponentLayout,
|
|
14308
|
+
resolveChainingParent: () => resolveChainingParent,
|
|
13563
14309
|
resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
|
|
13564
14310
|
restoreSpecFiles: () => restoreSpecFiles,
|
|
13565
14311
|
saveComponentSpec: () => saveComponentSpec,
|
|
@@ -13964,6 +14710,19 @@ function dryRunSerializeSpecs(include) {
|
|
|
13964
14710
|
function buildProjectGraph(level) {
|
|
13965
14711
|
return buildGraphModel(level);
|
|
13966
14712
|
}
|
|
14713
|
+
function resolveChainingParent() {
|
|
14714
|
+
return findChainingParent(getProjectRoot());
|
|
14715
|
+
}
|
|
14716
|
+
function computeStateIdAt(root) {
|
|
14717
|
+
const resolved = path14.resolve(root);
|
|
14718
|
+
return runWithProjectRoot(resolved, () => {
|
|
14719
|
+
workspaceFor(resolved).invalidate();
|
|
14720
|
+
const system = loadSystemSpec();
|
|
14721
|
+
if (!system) return null;
|
|
14722
|
+
const s = computeStateId();
|
|
14723
|
+
return `${s.algorithm}:${s.digest}`;
|
|
14724
|
+
});
|
|
14725
|
+
}
|
|
13967
14726
|
function deleteTypeSpec(id) {
|
|
13968
14727
|
return current().deleteTypeSpec(id);
|
|
13969
14728
|
}
|
|
@@ -14007,6 +14766,7 @@ var init_specs2 = __esm({
|
|
|
14007
14766
|
path14 = __toESM(require("path"));
|
|
14008
14767
|
init_loader();
|
|
14009
14768
|
init_fs();
|
|
14769
|
+
init_statehash();
|
|
14010
14770
|
init_yaml();
|
|
14011
14771
|
init_models();
|
|
14012
14772
|
init_narrative_labels();
|
|
@@ -16600,6 +17360,9 @@ function requireSpecs() {
|
|
|
16600
17360
|
function requireProvision() {
|
|
16601
17361
|
return init_provision(), __toCommonJS(provision_exports);
|
|
16602
17362
|
}
|
|
17363
|
+
function listExternalInterfaces2() {
|
|
17364
|
+
return listExternalInterfaces();
|
|
17365
|
+
}
|
|
16603
17366
|
function text(content) {
|
|
16604
17367
|
return { content: [{ type: "text", text: content }] };
|
|
16605
17368
|
}
|
|
@@ -17566,7 +18329,36 @@ NOTICE:
|
|
|
17566
18329
|
}
|
|
17567
18330
|
}
|
|
17568
18331
|
);
|
|
18332
|
+
reg(
|
|
18333
|
+
server,
|
|
18334
|
+
"sdd_list_external_interfaces",
|
|
18335
|
+
{
|
|
18336
|
+
description: "List the bound project's consumable external surfaces (parent family, siblings, foreign imports) as discovery entries with origin, provenance, and freshness \u2014 the tool an agent inside a subproject uses to SEE its outward world instead of discovering it by failed reference resolution. Full contracts stay in the vendored snapshots (.wai/surfaces/); each entry summarizes the interface ids it exposes."
|
|
18337
|
+
},
|
|
18338
|
+
() => {
|
|
18339
|
+
try {
|
|
18340
|
+
return json(listExternalInterfaces2());
|
|
18341
|
+
} catch (e) {
|
|
18342
|
+
return errText(String(e));
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
);
|
|
17569
18346
|
registerSkillResources(server);
|
|
18347
|
+
try {
|
|
18348
|
+
const chainingParent = resolveChainingParent();
|
|
18349
|
+
if (chainingParent) {
|
|
18350
|
+
let externalSurfaceCount = 0;
|
|
18351
|
+
try {
|
|
18352
|
+
externalSurfaceCount = listExternalInterfaces2().length;
|
|
18353
|
+
} catch {
|
|
18354
|
+
}
|
|
18355
|
+
process.stderr.write(
|
|
18356
|
+
`[wairon mcp] chained subproject: this root is mounted as subsystem "${chainingParent.subsystemId}" of the parent project at ${chainingParent.parentRoot} \u2014 ${externalSurfaceCount} vendored external surface(s) discoverable via sdd_list_external_interfaces
|
|
18357
|
+
`
|
|
18358
|
+
);
|
|
18359
|
+
}
|
|
18360
|
+
} catch {
|
|
18361
|
+
}
|
|
17570
18362
|
if (options.hostedTools) {
|
|
17571
18363
|
const hostedStub = () => errText("This hosted tool is dispatched by the hosting data plane before reaching the MCP server; it is unavailable outside a hosted request.");
|
|
17572
18364
|
reg(server, "sdd_host_lock_project", {
|
|
@@ -17702,6 +18494,8 @@ var init_server = __esm({
|
|
|
17702
18494
|
init_narrative_labels();
|
|
17703
18495
|
init_specs();
|
|
17704
18496
|
init_skills();
|
|
18497
|
+
init_specs2();
|
|
18498
|
+
init_surfaces();
|
|
17705
18499
|
SERVER_BUILD_STAMP = captureBuildStamp(__filename);
|
|
17706
18500
|
STALE_SERVER_WARNING = "\n\n\u26A0 STALE SERVER: the wairon build on disk changed after this MCP server started. Restart the MCP session (e.g. /mcp reconnect) before further spec edits \u2014 writes through a stale server can silently drop fields introduced by newer schemas.";
|
|
17707
18501
|
SKILL_RESOURCE_MIME = "text/markdown";
|
|
@@ -20775,7 +21569,7 @@ var require_dist = __commonJS({
|
|
|
20775
21569
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
20776
21570
|
}
|
|
20777
21571
|
var fs51 = __toESM2(require("fs"));
|
|
20778
|
-
var
|
|
21572
|
+
var path61 = __toESM2(require("path"));
|
|
20779
21573
|
var import_fflate = require_node();
|
|
20780
21574
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
20781
21575
|
function listEntries(archive) {
|
|
@@ -20813,8 +21607,8 @@ var require_dist = __commonJS({
|
|
|
20813
21607
|
}
|
|
20814
21608
|
function writeTree(destDir, files) {
|
|
20815
21609
|
for (const file of files) {
|
|
20816
|
-
const absolute =
|
|
20817
|
-
fs51.mkdirSync(
|
|
21610
|
+
const absolute = path61.join(destDir, file.path);
|
|
21611
|
+
fs51.mkdirSync(path61.dirname(absolute), { recursive: true });
|
|
20818
21612
|
fs51.writeFileSync(absolute, file.contents);
|
|
20819
21613
|
}
|
|
20820
21614
|
}
|
|
@@ -20827,10 +21621,10 @@ var require_dist = __commonJS({
|
|
|
20827
21621
|
for (const entry of fs51.readdirSync(current2, { withFileTypes: true })) {
|
|
20828
21622
|
if (entry.isDirectory()) {
|
|
20829
21623
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
20830
|
-
walkPackDir(root,
|
|
21624
|
+
walkPackDir(root, path61.join(current2, entry.name), out);
|
|
20831
21625
|
} else if (entry.isFile()) {
|
|
20832
|
-
const absolute =
|
|
20833
|
-
const relative22 =
|
|
21626
|
+
const absolute = path61.join(current2, entry.name);
|
|
21627
|
+
const relative22 = path61.relative(root, absolute).split(path61.sep).join("/");
|
|
20834
21628
|
out.push({ path: relative22, contents: fs51.readFileSync(absolute) });
|
|
20835
21629
|
}
|
|
20836
21630
|
}
|
|
@@ -22241,86 +23035,216 @@ async function generateLayer(options = {}) {
|
|
|
22241
23035
|
}
|
|
22242
23036
|
|
|
22243
23037
|
// src/commands/lock.ts
|
|
23038
|
+
var os7 = __toESM(require("os"));
|
|
22244
23039
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
22245
23040
|
init_logger();
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
23041
|
+
init_defaults();
|
|
23042
|
+
|
|
23043
|
+
// src/core/detection.ts
|
|
23044
|
+
var fs18 = __toESM(require("fs"));
|
|
23045
|
+
var path27 = __toESM(require("path"));
|
|
23046
|
+
init_defaults();
|
|
23047
|
+
var PACKAGE_MARKERS = [
|
|
23048
|
+
"package.json",
|
|
23049
|
+
"pyproject.toml",
|
|
23050
|
+
"Cargo.toml",
|
|
23051
|
+
"go.mod",
|
|
23052
|
+
"build.gradle",
|
|
23053
|
+
"build.gradle.kts",
|
|
23054
|
+
"pom.xml"
|
|
23055
|
+
];
|
|
23056
|
+
var MAX_SCAN_DEPTH = 5;
|
|
23057
|
+
function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
|
|
23058
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
23059
|
+
for (const c of detectGitSubmodules(projectRoot2)) {
|
|
23060
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22255
23061
|
}
|
|
22256
|
-
const
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
const promotable = collectPromotableSpecs(options.subsystem);
|
|
22260
|
-
const originalStatuses = /* @__PURE__ */ new Map();
|
|
22261
|
-
const isSpecInSubsystemScope = (specSubsystem) => {
|
|
22262
|
-
if (!options.subsystem) return true;
|
|
22263
|
-
if (!specSubsystem) return false;
|
|
22264
|
-
return specSubsystem === options.subsystem || specSubsystem.startsWith(`${options.subsystem}::`);
|
|
22265
|
-
};
|
|
22266
|
-
for (const s of index.subsystems) {
|
|
22267
|
-
if (!options.subsystem || s.id === options.subsystem || s.id.startsWith(`${options.subsystem}::`)) {
|
|
22268
|
-
originalStatuses.set(s, s.status);
|
|
22269
|
-
s.status = "complete";
|
|
23062
|
+
for (const c of detectNestedGitRepos(projectRoot2)) {
|
|
23063
|
+
if (!candidates.has(c.path)) {
|
|
23064
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22270
23065
|
}
|
|
22271
23066
|
}
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
23067
|
+
const gitPaths = new Set(
|
|
23068
|
+
Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
|
|
23069
|
+
);
|
|
23070
|
+
for (const c of detectPackageRoots(projectRoot2)) {
|
|
23071
|
+
if (candidates.has(c.path)) continue;
|
|
23072
|
+
const insideGit = Array.from(gitPaths).some(
|
|
23073
|
+
(gp) => c.path === gp || c.path.startsWith(gp + "/")
|
|
23074
|
+
);
|
|
23075
|
+
if (insideGit) continue;
|
|
23076
|
+
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
22277
23077
|
}
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
23078
|
+
const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
23079
|
+
return deduplicateIds(sorted, alreadyTrackedIds);
|
|
23080
|
+
}
|
|
23081
|
+
function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
|
|
23082
|
+
const idCount = /* @__PURE__ */ new Map();
|
|
23083
|
+
for (const id of existingIds) {
|
|
23084
|
+
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
22284
23085
|
}
|
|
22285
|
-
for (const
|
|
22286
|
-
|
|
22287
|
-
const comp = intf ? index.components.find((c) => c.id === intf.component) : null;
|
|
22288
|
-
if (comp && isSpecInSubsystemScope(comp.subsystem)) {
|
|
22289
|
-
originalStatuses.set(m, m.status);
|
|
22290
|
-
m.status = "complete";
|
|
22291
|
-
}
|
|
23086
|
+
for (const c of candidates) {
|
|
23087
|
+
idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
|
|
22292
23088
|
}
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
23089
|
+
return candidates.map((c) => {
|
|
23090
|
+
if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
|
|
23091
|
+
const parts = c.path.split("/");
|
|
23092
|
+
const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
|
|
23093
|
+
return { ...c, suggestedId: qualifiedId2 };
|
|
22298
23094
|
});
|
|
22299
|
-
|
|
22300
|
-
|
|
23095
|
+
}
|
|
23096
|
+
function parseGitmodules(filePath) {
|
|
23097
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
23098
|
+
const entries = [];
|
|
23099
|
+
let current2 = {};
|
|
23100
|
+
for (const line2 of content.split("\n")) {
|
|
23101
|
+
const trimmed = line2.trim();
|
|
23102
|
+
const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
|
|
23103
|
+
if (headerMatch) {
|
|
23104
|
+
if (current2.path) entries.push(current2);
|
|
23105
|
+
current2 = { name: headerMatch[1] };
|
|
23106
|
+
continue;
|
|
23107
|
+
}
|
|
23108
|
+
const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
23109
|
+
if (keyVal) {
|
|
23110
|
+
const [, key, value] = keyVal;
|
|
23111
|
+
if (key === "path") current2.path = value.trim();
|
|
23112
|
+
if (key === "url") current2.url = value.trim();
|
|
23113
|
+
}
|
|
22301
23114
|
}
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
23115
|
+
if (current2.path) entries.push(current2);
|
|
23116
|
+
return entries;
|
|
23117
|
+
}
|
|
23118
|
+
function detectGitSubmodules(projectRoot2) {
|
|
23119
|
+
const gitmodulesPath = path27.join(projectRoot2, ".gitmodules");
|
|
23120
|
+
if (!fs18.existsSync(gitmodulesPath)) return [];
|
|
23121
|
+
return parseGitmodules(gitmodulesPath).map((entry) => ({
|
|
23122
|
+
suggestedId: pathToId(entry.path),
|
|
23123
|
+
suggestedName: pathToName(entry.path),
|
|
23124
|
+
path: normalizePath3(entry.path),
|
|
23125
|
+
type: "git-submodule",
|
|
23126
|
+
alreadyTracked: false
|
|
23127
|
+
}));
|
|
23128
|
+
}
|
|
23129
|
+
function detectNestedGitRepos(projectRoot2) {
|
|
23130
|
+
const results = [];
|
|
23131
|
+
walkForGit(projectRoot2, projectRoot2, 0, results);
|
|
23132
|
+
return results;
|
|
23133
|
+
}
|
|
23134
|
+
function walkForGit(projectRoot2, currentDir, depth, results) {
|
|
23135
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
23136
|
+
let entries;
|
|
23137
|
+
try {
|
|
23138
|
+
entries = fs18.readdirSync(currentDir, { withFileTypes: true });
|
|
23139
|
+
} catch {
|
|
23140
|
+
return;
|
|
23141
|
+
}
|
|
23142
|
+
for (const entry of entries) {
|
|
23143
|
+
if (!entry.isDirectory()) continue;
|
|
23144
|
+
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23145
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
23146
|
+
const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
|
|
23147
|
+
if (relPath === "" || relPath === ".") continue;
|
|
23148
|
+
const gitPath = path27.join(fullPath, ".git");
|
|
23149
|
+
if (fs18.existsSync(gitPath)) {
|
|
23150
|
+
results.push({
|
|
23151
|
+
suggestedId: pathToId(relPath),
|
|
23152
|
+
suggestedName: pathToName(relPath),
|
|
23153
|
+
path: relPath,
|
|
23154
|
+
type: "git-repo",
|
|
23155
|
+
alreadyTracked: false
|
|
23156
|
+
});
|
|
23157
|
+
continue;
|
|
22315
23158
|
}
|
|
22316
|
-
|
|
22317
|
-
|
|
23159
|
+
walkForGit(projectRoot2, fullPath, depth + 1, results);
|
|
23160
|
+
}
|
|
23161
|
+
}
|
|
23162
|
+
function detectPackageRoots(projectRoot2) {
|
|
23163
|
+
const results = [];
|
|
23164
|
+
walkForPackages(projectRoot2, projectRoot2, 0, results);
|
|
23165
|
+
return results;
|
|
23166
|
+
}
|
|
23167
|
+
function walkForPackages(projectRoot2, currentDir, depth, results) {
|
|
23168
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
23169
|
+
let entries;
|
|
23170
|
+
try {
|
|
23171
|
+
entries = fs18.readdirSync(currentDir, { withFileTypes: true });
|
|
23172
|
+
} catch {
|
|
23173
|
+
return;
|
|
23174
|
+
}
|
|
23175
|
+
for (const entry of entries) {
|
|
23176
|
+
if (!entry.isDirectory()) continue;
|
|
23177
|
+
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23178
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
23179
|
+
const relPath = normalizePath3(path27.relative(projectRoot2, fullPath));
|
|
23180
|
+
if (relPath === "" || relPath === ".") continue;
|
|
23181
|
+
const hasMarker = PACKAGE_MARKERS.some((m) => fs18.existsSync(path27.join(fullPath, m)));
|
|
23182
|
+
if (hasMarker) {
|
|
23183
|
+
results.push({
|
|
23184
|
+
suggestedId: pathToId(relPath),
|
|
23185
|
+
suggestedName: pathToName(relPath),
|
|
23186
|
+
path: relPath,
|
|
23187
|
+
type: "package-root",
|
|
23188
|
+
alreadyTracked: false
|
|
23189
|
+
});
|
|
22318
23190
|
}
|
|
22319
|
-
|
|
22320
|
-
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
22321
|
-
process.exit(1);
|
|
23191
|
+
walkForPackages(projectRoot2, fullPath, depth + 1, results);
|
|
22322
23192
|
}
|
|
22323
|
-
|
|
23193
|
+
}
|
|
23194
|
+
function pathToId(relPath) {
|
|
23195
|
+
const basename11 = path27.basename(relPath);
|
|
23196
|
+
return basename11.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
23197
|
+
}
|
|
23198
|
+
function pathToName(relPath) {
|
|
23199
|
+
const id = pathToId(relPath);
|
|
23200
|
+
return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
23201
|
+
}
|
|
23202
|
+
function normalizePath3(p) {
|
|
23203
|
+
return p.replace(/\\/g, "/");
|
|
23204
|
+
}
|
|
23205
|
+
|
|
23206
|
+
// src/core/index.ts
|
|
23207
|
+
init_domains();
|
|
23208
|
+
init_validation();
|
|
23209
|
+
init_extensions();
|
|
23210
|
+
init_variants();
|
|
23211
|
+
init_rules();
|
|
23212
|
+
init_specs2();
|
|
23213
|
+
init_provision();
|
|
23214
|
+
init_diagram();
|
|
23215
|
+
|
|
23216
|
+
// src/core/lockfile.ts
|
|
23217
|
+
var fs19 = __toESM(require("fs"));
|
|
23218
|
+
var path28 = __toESM(require("path"));
|
|
23219
|
+
init_fs();
|
|
23220
|
+
function lockPath() {
|
|
23221
|
+
return aiDir("lock.json");
|
|
23222
|
+
}
|
|
23223
|
+
function readLockRecord() {
|
|
23224
|
+
try {
|
|
23225
|
+
return JSON.parse(fs19.readFileSync(lockPath(), "utf8"));
|
|
23226
|
+
} catch {
|
|
23227
|
+
return null;
|
|
23228
|
+
}
|
|
23229
|
+
}
|
|
23230
|
+
function writeLockRecord(record2) {
|
|
23231
|
+
const p = lockPath();
|
|
23232
|
+
fs19.mkdirSync(path28.dirname(p), { recursive: true });
|
|
23233
|
+
const tmp = `${p}.tmp`;
|
|
23234
|
+
fs19.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
|
|
23235
|
+
fs19.renameSync(tmp, p);
|
|
23236
|
+
}
|
|
23237
|
+
|
|
23238
|
+
// src/core/index.ts
|
|
23239
|
+
init_statehash();
|
|
23240
|
+
init_agent_resolver();
|
|
23241
|
+
init_skills();
|
|
23242
|
+
init_surfaces();
|
|
23243
|
+
init_openapi();
|
|
23244
|
+
|
|
23245
|
+
// src/commands/lock.ts
|
|
23246
|
+
async function runLock(options = {}, gate) {
|
|
23247
|
+
const promotable = collectPromotableSpecs(options.subsystem);
|
|
22324
23248
|
if (promotable.length === 0) {
|
|
22325
23249
|
logger.info("All specs are already complete \u2014 this will re-validate and regenerate the agent topology.");
|
|
22326
23250
|
} else {
|
|
@@ -22344,23 +23268,36 @@ async function runLock(options = {}) {
|
|
|
22344
23268
|
default: false
|
|
22345
23269
|
}
|
|
22346
23270
|
]);
|
|
22347
|
-
if (!confirmed)
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
23271
|
+
if (!confirmed) return null;
|
|
23272
|
+
}
|
|
23273
|
+
if (options.subsystem) {
|
|
23274
|
+
for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
|
|
23275
|
+
invalidateSpecCache();
|
|
23276
|
+
} else {
|
|
23277
|
+
promoteAllComplete();
|
|
22351
23278
|
}
|
|
22352
|
-
for (const p of promotable) applySpecStatus(p.kind, p.id, "complete");
|
|
22353
|
-
invalidateSpecCache();
|
|
22354
23279
|
if (promotable.length > 0) {
|
|
22355
23280
|
logger.success(`Locked ${promotable.length} spec(s) as complete.`);
|
|
22356
23281
|
}
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
23282
|
+
let lockedBy = "local";
|
|
23283
|
+
try {
|
|
23284
|
+
lockedBy = `local:${os7.userInfo().username}`;
|
|
23285
|
+
} catch {
|
|
23286
|
+
}
|
|
23287
|
+
const record2 = {
|
|
23288
|
+
stateId: computeStateId(),
|
|
23289
|
+
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23290
|
+
lockedBy,
|
|
23291
|
+
validatorVersion: WAIRON_VERSION,
|
|
23292
|
+
validationResult: {
|
|
23293
|
+
valid: true,
|
|
23294
|
+
errors: 0,
|
|
23295
|
+
warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0
|
|
23296
|
+
},
|
|
23297
|
+
status: "ready"
|
|
23298
|
+
};
|
|
23299
|
+
writeLockRecord(record2);
|
|
23300
|
+
return record2;
|
|
22364
23301
|
}
|
|
22365
23302
|
|
|
22366
23303
|
// src/commands/validate.ts
|
|
@@ -22499,6 +23436,10 @@ async function runValidate(options = {}) {
|
|
|
22499
23436
|
}
|
|
22500
23437
|
}
|
|
22501
23438
|
|
|
23439
|
+
// src/cli/index.ts
|
|
23440
|
+
init_loader();
|
|
23441
|
+
init_fs();
|
|
23442
|
+
|
|
22502
23443
|
// src/commands/list.ts
|
|
22503
23444
|
var import_chalk7 = __toESM(require("chalk"));
|
|
22504
23445
|
init_logger();
|
|
@@ -22613,9 +23554,9 @@ init_mcp();
|
|
|
22613
23554
|
// src/commands/update.ts
|
|
22614
23555
|
var https = __toESM(require("https"));
|
|
22615
23556
|
var http = __toESM(require("http"));
|
|
22616
|
-
var
|
|
22617
|
-
var
|
|
22618
|
-
var
|
|
23557
|
+
var fs20 = __toESM(require("fs"));
|
|
23558
|
+
var path29 = __toESM(require("path"));
|
|
23559
|
+
var os8 = __toESM(require("os"));
|
|
22619
23560
|
var crypto2 = __toESM(require("crypto"));
|
|
22620
23561
|
var import_child_process2 = require("child_process");
|
|
22621
23562
|
init_logger();
|
|
@@ -22678,8 +23619,8 @@ async function runUpdate(options = {}) {
|
|
|
22678
23619
|
logger.info(`Download manually from: ${release.html_url}`);
|
|
22679
23620
|
process.exit(1);
|
|
22680
23621
|
}
|
|
22681
|
-
const tmpDir =
|
|
22682
|
-
const tmpFile =
|
|
23622
|
+
const tmpDir = os8.tmpdir();
|
|
23623
|
+
const tmpFile = path29.join(tmpDir, assetName);
|
|
22683
23624
|
logger.info(`Downloading ${assetName}...`);
|
|
22684
23625
|
try {
|
|
22685
23626
|
await downloadFile(asset.browser_download_url, tmpFile);
|
|
@@ -22696,16 +23637,16 @@ async function runUpdate(options = {}) {
|
|
|
22696
23637
|
const checksumAssetName = assetName + ".sha256";
|
|
22697
23638
|
const checksumAsset = release.assets.find((a) => a.name === checksumAssetName);
|
|
22698
23639
|
if (checksumAsset) {
|
|
22699
|
-
const tmpChecksum =
|
|
23640
|
+
const tmpChecksum = path29.join(tmpDir, checksumAssetName);
|
|
22700
23641
|
logger.info(`Verifying checksum...`);
|
|
22701
23642
|
try {
|
|
22702
23643
|
await downloadFile(checksumAsset.browser_download_url, tmpChecksum);
|
|
22703
23644
|
verifyChecksum(tmpFile, tmpChecksum, assetName);
|
|
22704
|
-
|
|
23645
|
+
fs20.unlinkSync(tmpChecksum);
|
|
22705
23646
|
} catch (err) {
|
|
22706
23647
|
logger.error(`Checksum verification failed: ${err.message}`);
|
|
22707
23648
|
try {
|
|
22708
|
-
|
|
23649
|
+
fs20.unlinkSync(tmpFile);
|
|
22709
23650
|
} catch {
|
|
22710
23651
|
}
|
|
22711
23652
|
process.exit(1);
|
|
@@ -22771,7 +23712,7 @@ function fetchReleases(repo) {
|
|
|
22771
23712
|
}
|
|
22772
23713
|
function downloadFile(url, dest) {
|
|
22773
23714
|
return new Promise((resolve24, reject) => {
|
|
22774
|
-
const file =
|
|
23715
|
+
const file = fs20.createWriteStream(dest);
|
|
22775
23716
|
const get3 = url.startsWith("https://") ? https.get : http.get;
|
|
22776
23717
|
get3(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
22777
23718
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
@@ -22793,21 +23734,21 @@ function downloadFile(url, dest) {
|
|
|
22793
23734
|
});
|
|
22794
23735
|
file.on("error", (err) => {
|
|
22795
23736
|
res.destroy();
|
|
22796
|
-
|
|
23737
|
+
fs20.unlink(dest, () => {
|
|
22797
23738
|
});
|
|
22798
23739
|
reject(err);
|
|
22799
23740
|
});
|
|
22800
23741
|
}).on("error", (err) => {
|
|
22801
|
-
|
|
23742
|
+
fs20.unlink(dest, () => {
|
|
22802
23743
|
});
|
|
22803
23744
|
reject(err);
|
|
22804
23745
|
});
|
|
22805
23746
|
});
|
|
22806
23747
|
}
|
|
22807
23748
|
function verifyChecksum(filePath, checksumFile, expectedFilename) {
|
|
22808
|
-
const checksumContent =
|
|
23749
|
+
const checksumContent = fs20.readFileSync(checksumFile, "utf-8").trim();
|
|
22809
23750
|
const expectedHash = checksumContent.split(/\s+/)[0].toLowerCase();
|
|
22810
|
-
const fileBuffer =
|
|
23751
|
+
const fileBuffer = fs20.readFileSync(filePath);
|
|
22811
23752
|
const actualHash = crypto2.createHash("sha256").update(fileBuffer).digest("hex").toLowerCase();
|
|
22812
23753
|
if (actualHash !== expectedHash) {
|
|
22813
23754
|
throw new Error(
|
|
@@ -22838,9 +23779,9 @@ function isPkgBinary2() {
|
|
|
22838
23779
|
function installBinary(tmpFile, destPath) {
|
|
22839
23780
|
const platform = process.platform;
|
|
22840
23781
|
const isZip = tmpFile.endsWith(".zip");
|
|
22841
|
-
const extractDir =
|
|
22842
|
-
if (
|
|
22843
|
-
|
|
23782
|
+
const extractDir = path29.join(os8.tmpdir(), "wairon-extract");
|
|
23783
|
+
if (fs20.existsSync(extractDir)) fs20.rmSync(extractDir, { recursive: true });
|
|
23784
|
+
fs20.mkdirSync(extractDir, { recursive: true });
|
|
22844
23785
|
if (isZip) {
|
|
22845
23786
|
(0, import_child_process2.execSync)(
|
|
22846
23787
|
`powershell -NoProfile -NonInteractive -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${extractDir}' -Force"`,
|
|
@@ -22850,18 +23791,18 @@ function installBinary(tmpFile, destPath) {
|
|
|
22850
23791
|
(0, import_child_process2.execSync)(`tar -xzf "${tmpFile}" -C "${extractDir}"`, { stdio: ["ignore", "pipe", "pipe"] });
|
|
22851
23792
|
}
|
|
22852
23793
|
const binaryName = platform === "win32" ? "wairon.exe" : "wairon";
|
|
22853
|
-
const extractedBinary =
|
|
22854
|
-
if (!
|
|
23794
|
+
const extractedBinary = path29.join(extractDir, binaryName);
|
|
23795
|
+
if (!fs20.existsSync(extractedBinary)) {
|
|
22855
23796
|
throw new Error(`Extracted binary not found at ${extractedBinary}`);
|
|
22856
23797
|
}
|
|
22857
23798
|
if (platform === "win32") {
|
|
22858
23799
|
const oldPath = destPath + ".old";
|
|
22859
23800
|
try {
|
|
22860
23801
|
cleanStaleBinary(oldPath);
|
|
22861
|
-
|
|
22862
|
-
|
|
23802
|
+
fs20.renameSync(destPath, oldPath);
|
|
23803
|
+
fs20.copyFileSync(extractedBinary, destPath);
|
|
22863
23804
|
try {
|
|
22864
|
-
|
|
23805
|
+
fs20.unlinkSync(oldPath);
|
|
22865
23806
|
} catch {
|
|
22866
23807
|
}
|
|
22867
23808
|
} catch (err) {
|
|
@@ -22875,25 +23816,25 @@ function installBinary(tmpFile, destPath) {
|
|
|
22875
23816
|
}
|
|
22876
23817
|
} else {
|
|
22877
23818
|
const tmpDest = destPath + ".new";
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
23819
|
+
fs20.copyFileSync(extractedBinary, tmpDest);
|
|
23820
|
+
fs20.chmodSync(tmpDest, 493);
|
|
23821
|
+
fs20.renameSync(tmpDest, destPath);
|
|
22881
23822
|
}
|
|
22882
23823
|
try {
|
|
22883
|
-
|
|
23824
|
+
fs20.unlinkSync(tmpFile);
|
|
22884
23825
|
} catch {
|
|
22885
23826
|
}
|
|
22886
23827
|
try {
|
|
22887
|
-
|
|
23828
|
+
fs20.rmSync(extractDir, { recursive: true });
|
|
22888
23829
|
} catch {
|
|
22889
23830
|
}
|
|
22890
23831
|
}
|
|
22891
23832
|
function cleanStaleBinary(oldPath) {
|
|
22892
23833
|
const target = oldPath ?? (isPkgBinary2() ? process.execPath + ".old" : null);
|
|
22893
23834
|
if (!target) return;
|
|
22894
|
-
if (
|
|
23835
|
+
if (fs20.existsSync(target)) {
|
|
22895
23836
|
try {
|
|
22896
|
-
|
|
23837
|
+
fs20.unlinkSync(target);
|
|
22897
23838
|
} catch {
|
|
22898
23839
|
}
|
|
22899
23840
|
}
|
|
@@ -23139,171 +24080,6 @@ async function filteredCheckbox(config) {
|
|
|
23139
24080
|
|
|
23140
24081
|
// src/commands/domains.ts
|
|
23141
24082
|
init_loader();
|
|
23142
|
-
|
|
23143
|
-
// src/core/detection.ts
|
|
23144
|
-
var fs19 = __toESM(require("fs"));
|
|
23145
|
-
var path28 = __toESM(require("path"));
|
|
23146
|
-
init_defaults();
|
|
23147
|
-
var PACKAGE_MARKERS = [
|
|
23148
|
-
"package.json",
|
|
23149
|
-
"pyproject.toml",
|
|
23150
|
-
"Cargo.toml",
|
|
23151
|
-
"go.mod",
|
|
23152
|
-
"build.gradle",
|
|
23153
|
-
"build.gradle.kts",
|
|
23154
|
-
"pom.xml"
|
|
23155
|
-
];
|
|
23156
|
-
var MAX_SCAN_DEPTH = 5;
|
|
23157
|
-
function detectDomainCandidates(projectRoot2, alreadyTrackedPaths = /* @__PURE__ */ new Set(), alreadyTrackedIds = /* @__PURE__ */ new Set()) {
|
|
23158
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
23159
|
-
for (const c of detectGitSubmodules(projectRoot2)) {
|
|
23160
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23161
|
-
}
|
|
23162
|
-
for (const c of detectNestedGitRepos(projectRoot2)) {
|
|
23163
|
-
if (!candidates.has(c.path)) {
|
|
23164
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23165
|
-
}
|
|
23166
|
-
}
|
|
23167
|
-
const gitPaths = new Set(
|
|
23168
|
-
Array.from(candidates.values()).filter((c) => c.type === "git-submodule" || c.type === "git-repo").map((c) => c.path)
|
|
23169
|
-
);
|
|
23170
|
-
for (const c of detectPackageRoots(projectRoot2)) {
|
|
23171
|
-
if (candidates.has(c.path)) continue;
|
|
23172
|
-
const insideGit = Array.from(gitPaths).some(
|
|
23173
|
-
(gp) => c.path === gp || c.path.startsWith(gp + "/")
|
|
23174
|
-
);
|
|
23175
|
-
if (insideGit) continue;
|
|
23176
|
-
candidates.set(c.path, { ...c, alreadyTracked: alreadyTrackedPaths.has(c.path) });
|
|
23177
|
-
}
|
|
23178
|
-
const sorted = Array.from(candidates.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
23179
|
-
return deduplicateIds(sorted, alreadyTrackedIds);
|
|
23180
|
-
}
|
|
23181
|
-
function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
|
|
23182
|
-
const idCount = /* @__PURE__ */ new Map();
|
|
23183
|
-
for (const id of existingIds) {
|
|
23184
|
-
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
23185
|
-
}
|
|
23186
|
-
for (const c of candidates) {
|
|
23187
|
-
idCount.set(c.suggestedId, (idCount.get(c.suggestedId) ?? 0) + 1);
|
|
23188
|
-
}
|
|
23189
|
-
return candidates.map((c) => {
|
|
23190
|
-
if ((idCount.get(c.suggestedId) ?? 0) <= 1) return c;
|
|
23191
|
-
const parts = c.path.split("/");
|
|
23192
|
-
const qualifiedId2 = parts.length >= 2 ? pathToId(`${parts[parts.length - 2]}-${parts[parts.length - 1]}`) : c.suggestedId;
|
|
23193
|
-
return { ...c, suggestedId: qualifiedId2 };
|
|
23194
|
-
});
|
|
23195
|
-
}
|
|
23196
|
-
function parseGitmodules(filePath) {
|
|
23197
|
-
const content = fs19.readFileSync(filePath, "utf-8");
|
|
23198
|
-
const entries = [];
|
|
23199
|
-
let current2 = {};
|
|
23200
|
-
for (const line2 of content.split("\n")) {
|
|
23201
|
-
const trimmed = line2.trim();
|
|
23202
|
-
const headerMatch = trimmed.match(/^\[submodule "(.+)"\]$/);
|
|
23203
|
-
if (headerMatch) {
|
|
23204
|
-
if (current2.path) entries.push(current2);
|
|
23205
|
-
current2 = { name: headerMatch[1] };
|
|
23206
|
-
continue;
|
|
23207
|
-
}
|
|
23208
|
-
const keyVal = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
23209
|
-
if (keyVal) {
|
|
23210
|
-
const [, key, value] = keyVal;
|
|
23211
|
-
if (key === "path") current2.path = value.trim();
|
|
23212
|
-
if (key === "url") current2.url = value.trim();
|
|
23213
|
-
}
|
|
23214
|
-
}
|
|
23215
|
-
if (current2.path) entries.push(current2);
|
|
23216
|
-
return entries;
|
|
23217
|
-
}
|
|
23218
|
-
function detectGitSubmodules(projectRoot2) {
|
|
23219
|
-
const gitmodulesPath = path28.join(projectRoot2, ".gitmodules");
|
|
23220
|
-
if (!fs19.existsSync(gitmodulesPath)) return [];
|
|
23221
|
-
return parseGitmodules(gitmodulesPath).map((entry) => ({
|
|
23222
|
-
suggestedId: pathToId(entry.path),
|
|
23223
|
-
suggestedName: pathToName(entry.path),
|
|
23224
|
-
path: normalizePath3(entry.path),
|
|
23225
|
-
type: "git-submodule",
|
|
23226
|
-
alreadyTracked: false
|
|
23227
|
-
}));
|
|
23228
|
-
}
|
|
23229
|
-
function detectNestedGitRepos(projectRoot2) {
|
|
23230
|
-
const results = [];
|
|
23231
|
-
walkForGit(projectRoot2, projectRoot2, 0, results);
|
|
23232
|
-
return results;
|
|
23233
|
-
}
|
|
23234
|
-
function walkForGit(projectRoot2, currentDir, depth, results) {
|
|
23235
|
-
if (depth > MAX_SCAN_DEPTH) return;
|
|
23236
|
-
let entries;
|
|
23237
|
-
try {
|
|
23238
|
-
entries = fs19.readdirSync(currentDir, { withFileTypes: true });
|
|
23239
|
-
} catch {
|
|
23240
|
-
return;
|
|
23241
|
-
}
|
|
23242
|
-
for (const entry of entries) {
|
|
23243
|
-
if (!entry.isDirectory()) continue;
|
|
23244
|
-
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23245
|
-
const fullPath = path28.join(currentDir, entry.name);
|
|
23246
|
-
const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
|
|
23247
|
-
if (relPath === "" || relPath === ".") continue;
|
|
23248
|
-
const gitPath = path28.join(fullPath, ".git");
|
|
23249
|
-
if (fs19.existsSync(gitPath)) {
|
|
23250
|
-
results.push({
|
|
23251
|
-
suggestedId: pathToId(relPath),
|
|
23252
|
-
suggestedName: pathToName(relPath),
|
|
23253
|
-
path: relPath,
|
|
23254
|
-
type: "git-repo",
|
|
23255
|
-
alreadyTracked: false
|
|
23256
|
-
});
|
|
23257
|
-
continue;
|
|
23258
|
-
}
|
|
23259
|
-
walkForGit(projectRoot2, fullPath, depth + 1, results);
|
|
23260
|
-
}
|
|
23261
|
-
}
|
|
23262
|
-
function detectPackageRoots(projectRoot2) {
|
|
23263
|
-
const results = [];
|
|
23264
|
-
walkForPackages(projectRoot2, projectRoot2, 0, results);
|
|
23265
|
-
return results;
|
|
23266
|
-
}
|
|
23267
|
-
function walkForPackages(projectRoot2, currentDir, depth, results) {
|
|
23268
|
-
if (depth > MAX_SCAN_DEPTH) return;
|
|
23269
|
-
let entries;
|
|
23270
|
-
try {
|
|
23271
|
-
entries = fs19.readdirSync(currentDir, { withFileTypes: true });
|
|
23272
|
-
} catch {
|
|
23273
|
-
return;
|
|
23274
|
-
}
|
|
23275
|
-
for (const entry of entries) {
|
|
23276
|
-
if (!entry.isDirectory()) continue;
|
|
23277
|
-
if (SCAN_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
23278
|
-
const fullPath = path28.join(currentDir, entry.name);
|
|
23279
|
-
const relPath = normalizePath3(path28.relative(projectRoot2, fullPath));
|
|
23280
|
-
if (relPath === "" || relPath === ".") continue;
|
|
23281
|
-
const hasMarker = PACKAGE_MARKERS.some((m) => fs19.existsSync(path28.join(fullPath, m)));
|
|
23282
|
-
if (hasMarker) {
|
|
23283
|
-
results.push({
|
|
23284
|
-
suggestedId: pathToId(relPath),
|
|
23285
|
-
suggestedName: pathToName(relPath),
|
|
23286
|
-
path: relPath,
|
|
23287
|
-
type: "package-root",
|
|
23288
|
-
alreadyTracked: false
|
|
23289
|
-
});
|
|
23290
|
-
}
|
|
23291
|
-
walkForPackages(projectRoot2, fullPath, depth + 1, results);
|
|
23292
|
-
}
|
|
23293
|
-
}
|
|
23294
|
-
function pathToId(relPath) {
|
|
23295
|
-
const basename12 = path28.basename(relPath);
|
|
23296
|
-
return basename12.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
23297
|
-
}
|
|
23298
|
-
function pathToName(relPath) {
|
|
23299
|
-
const id = pathToId(relPath);
|
|
23300
|
-
return id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
23301
|
-
}
|
|
23302
|
-
function normalizePath3(p) {
|
|
23303
|
-
return p.replace(/\\/g, "/");
|
|
23304
|
-
}
|
|
23305
|
-
|
|
23306
|
-
// src/commands/domains.ts
|
|
23307
24083
|
init_domains();
|
|
23308
24084
|
init_domain();
|
|
23309
24085
|
async function runDomainsList() {
|
|
@@ -23493,9 +24269,9 @@ async function runSkillsInstall() {
|
|
|
23493
24269
|
}
|
|
23494
24270
|
|
|
23495
24271
|
// src/commands/doctor.ts
|
|
23496
|
-
var
|
|
23497
|
-
var
|
|
23498
|
-
var
|
|
24272
|
+
var fs21 = __toESM(require("fs"));
|
|
24273
|
+
var os9 = __toESM(require("os"));
|
|
24274
|
+
var path30 = __toESM(require("path"));
|
|
23499
24275
|
var import_chalk12 = __toESM(require("chalk"));
|
|
23500
24276
|
init_logger();
|
|
23501
24277
|
init_defaults();
|
|
@@ -23525,10 +24301,10 @@ function stampVerdict(content) {
|
|
|
23525
24301
|
return { mark: "warn", note: `v${v} \u2014 stale, installed is v${WAIRON_VERSION}` };
|
|
23526
24302
|
}
|
|
23527
24303
|
function mcpEntryHealth(settingsPath) {
|
|
23528
|
-
if (!
|
|
24304
|
+
if (!fs21.existsSync(settingsPath)) return { mark: "warn", note: "not registered" };
|
|
23529
24305
|
let entry;
|
|
23530
24306
|
try {
|
|
23531
|
-
const s = JSON.parse(
|
|
24307
|
+
const s = JSON.parse(fs21.readFileSync(settingsPath, "utf8"));
|
|
23532
24308
|
entry = s.mcpServers?.["wairon"];
|
|
23533
24309
|
} catch {
|
|
23534
24310
|
return { mark: "error", note: "parse error" };
|
|
@@ -23536,7 +24312,7 @@ function mcpEntryHealth(settingsPath) {
|
|
|
23536
24312
|
if (!entry) return { mark: "warn", note: "not registered" };
|
|
23537
24313
|
if (entry.command === "node" && Array.isArray(entry.args) && typeof entry.args[0] === "string") {
|
|
23538
24314
|
const scriptPath = entry.args[0];
|
|
23539
|
-
if (!
|
|
24315
|
+
if (!fs21.existsSync(scriptPath)) {
|
|
23540
24316
|
return { mark: "error", note: `registered but the server path is missing \u2014 ${scriptPath}` };
|
|
23541
24317
|
}
|
|
23542
24318
|
}
|
|
@@ -23588,7 +24364,7 @@ async function runDoctor(options = {}) {
|
|
|
23588
24364
|
const { findChainingSubprojectsMissingConfig: findChainingSubprojectsMissingConfig2 } = (init_provision(), __toCommonJS(provision_exports));
|
|
23589
24365
|
const missing = findChainingSubprojectsMissingConfig2(getProjectRoot());
|
|
23590
24366
|
if (missing.length > 0) {
|
|
23591
|
-
line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) =>
|
|
24367
|
+
line(tally, "warn", `${missing.length} chained subproject(s) have specs but no project.yaml (un-runnable standalone): ${missing.map((d) => path30.relative(getProjectRoot(), d) || ".").join(", ")}. Run \`wairon doctor --fix\` to initialize them.`);
|
|
23592
24368
|
}
|
|
23593
24369
|
} catch {
|
|
23594
24370
|
}
|
|
@@ -23626,7 +24402,7 @@ async function runDoctor(options = {}) {
|
|
|
23626
24402
|
const gp = localGuideFilePath(process.cwd(), t);
|
|
23627
24403
|
if (!gp || seenGuides.has(gp)) continue;
|
|
23628
24404
|
seenGuides.add(gp);
|
|
23629
|
-
const rel2 =
|
|
24405
|
+
const rel2 = path30.relative(process.cwd(), gp).replace(/\\/g, "/");
|
|
23630
24406
|
if (!pathExists(gp)) {
|
|
23631
24407
|
line(tally, "warn", `${rel2} guide \u2014 not injected (run \`wairon generate\`)`);
|
|
23632
24408
|
continue;
|
|
@@ -23660,17 +24436,17 @@ async function runDoctor(options = {}) {
|
|
|
23660
24436
|
line(tally, h.mark, `Claude (project .mcp.json): ${h.note}${h.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend claude`"}`);
|
|
23661
24437
|
}
|
|
23662
24438
|
if (wantGemini) {
|
|
23663
|
-
const globalCfg =
|
|
24439
|
+
const globalCfg = path30.join(os9.homedir(), ".gemini", "antigravity-cli", "mcp_config.json");
|
|
23664
24440
|
const hg = mcpEntryHealth(globalCfg);
|
|
23665
24441
|
line(tally, hg.mark, `Antigravity (global mcp_config.json): ${hg.note}${hg.mark === "ok" ? "" : " \u2014 run `wairon mcp install --backend gemini --global`"}`);
|
|
23666
24442
|
const projPath = fromProjectRoot(".gemini", "settings.json");
|
|
23667
|
-
if (
|
|
24443
|
+
if (fs21.existsSync(projPath)) {
|
|
23668
24444
|
const hp = mcpEntryHealth(projPath);
|
|
23669
24445
|
line(tally, hp.mark === "error" ? "error" : "ok", `Gemini CLI (project): ${hp.note} ${import_chalk12.default.gray("(Antigravity ignores this file)")}`);
|
|
23670
24446
|
}
|
|
23671
24447
|
}
|
|
23672
|
-
const pluginDir =
|
|
23673
|
-
if (
|
|
24448
|
+
const pluginDir = path30.join(os9.homedir(), ".gemini", "config", "plugins", "wairon");
|
|
24449
|
+
if (fs21.existsSync(pluginDir)) {
|
|
23674
24450
|
line(tally, "warn", `Legacy Antigravity plugin present (${pluginDir}) \u2014 it collides with the wairon MCP server. Remove it with \`wairon doctor --fix\`.`);
|
|
23675
24451
|
}
|
|
23676
24452
|
logger.blank();
|
|
@@ -23701,7 +24477,7 @@ async function applyFixes() {
|
|
|
23701
24477
|
const legacySpecs = findLegacySpecFiles();
|
|
23702
24478
|
if (legacySpecs.length > 0) {
|
|
23703
24479
|
for (const { path: oldPath, expected: newPath } of legacySpecs) {
|
|
23704
|
-
|
|
24480
|
+
fs21.renameSync(oldPath, newPath);
|
|
23705
24481
|
}
|
|
23706
24482
|
console.log(` ${icon("ok")} Migrated ${legacySpecs.length} legacy spec file(s) to the new dot-prefixed unified schema.`);
|
|
23707
24483
|
}
|
|
@@ -23755,8 +24531,8 @@ function printSummary(tally) {
|
|
|
23755
24531
|
}
|
|
23756
24532
|
|
|
23757
24533
|
// src/commands/diagram.ts
|
|
23758
|
-
var
|
|
23759
|
-
var
|
|
24534
|
+
var fs22 = __toESM(require("fs"));
|
|
24535
|
+
var path31 = __toESM(require("path"));
|
|
23760
24536
|
init_logger();
|
|
23761
24537
|
init_loader();
|
|
23762
24538
|
init_fs();
|
|
@@ -23791,8 +24567,8 @@ function collectIssues() {
|
|
|
23791
24567
|
}
|
|
23792
24568
|
function writeCanvas(dest) {
|
|
23793
24569
|
const model = buildCanvasModel(collectIssues());
|
|
23794
|
-
ensureDir(
|
|
23795
|
-
|
|
24570
|
+
ensureDir(path31.dirname(path31.resolve(dest)));
|
|
24571
|
+
fs22.writeFileSync(dest, renderCanvasHtml(model), "utf-8");
|
|
23796
24572
|
}
|
|
23797
24573
|
function parseSequenceRef(ref) {
|
|
23798
24574
|
const sep6 = ref.includes(":") ? ref.lastIndexOf(":") : ref.lastIndexOf(".");
|
|
@@ -23807,55 +24583,55 @@ async function runDiagram(rawOptions = {}) {
|
|
|
23807
24583
|
assertProjectInitialized();
|
|
23808
24584
|
const options = applyFormat(rawOptions);
|
|
23809
24585
|
if (options.canvas && !options.all) {
|
|
23810
|
-
const dest2 = options.out ??
|
|
24586
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
23811
24587
|
writeCanvas(dest2);
|
|
23812
24588
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
23813
24589
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
|
|
23814
24590
|
return;
|
|
23815
24591
|
}
|
|
23816
24592
|
if (options.drawio && !options.all) {
|
|
23817
|
-
const dest2 = options.out ??
|
|
23818
|
-
ensureDir(
|
|
23819
|
-
|
|
24593
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.drawio");
|
|
24594
|
+
ensureDir(path31.dirname(path31.resolve(dest2)));
|
|
24595
|
+
fs22.writeFileSync(dest2, generateDrawioXml(buildCanvasModel()), "utf-8");
|
|
23820
24596
|
logger.success(`draw.io diagram written to ${dest2}`);
|
|
23821
24597
|
logger.info("Open with draw.io / diagrams.net (or import into tools that accept the format).");
|
|
23822
24598
|
return;
|
|
23823
24599
|
}
|
|
23824
24600
|
if (options.excalidraw && !options.all) {
|
|
23825
|
-
const dest2 = options.out ??
|
|
23826
|
-
ensureDir(
|
|
23827
|
-
|
|
24601
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "architecture.excalidraw");
|
|
24602
|
+
ensureDir(path31.dirname(path31.resolve(dest2)));
|
|
24603
|
+
fs22.writeFileSync(dest2, generateExcalidrawScene(buildCanvasModel()), "utf-8");
|
|
23828
24604
|
logger.success(`Excalidraw scene written to ${dest2}`);
|
|
23829
24605
|
logger.info("Open with excalidraw.com or the VS Code extension.");
|
|
23830
24606
|
return;
|
|
23831
24607
|
}
|
|
23832
24608
|
const wantsMermaid = options.format?.toLowerCase().startsWith("mermaid") || !!options.subsystem || !!options.sequence;
|
|
23833
24609
|
if (!options.all && !options.sequence && !wantsMermaid) {
|
|
23834
|
-
const dest2 = options.out ??
|
|
24610
|
+
const dest2 = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
23835
24611
|
writeCanvas(dest2);
|
|
23836
24612
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
23837
24613
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
|
|
23838
24614
|
return;
|
|
23839
24615
|
}
|
|
23840
24616
|
if (options.all) {
|
|
23841
|
-
const outDir = options.out ??
|
|
24617
|
+
const outDir = options.out ?? path31.join(AI_PATHS.docsDir(), "diagrams");
|
|
23842
24618
|
const files = generateDiagramSet();
|
|
23843
24619
|
if (files.length === 0) {
|
|
23844
24620
|
logger.warn("No diagrams to generate \u2014 the spec tree has no components yet.");
|
|
23845
24621
|
return;
|
|
23846
24622
|
}
|
|
23847
24623
|
for (const file of files) {
|
|
23848
|
-
const dest2 =
|
|
23849
|
-
ensureDir(
|
|
23850
|
-
|
|
24624
|
+
const dest2 = path31.join(outDir, file.relPath);
|
|
24625
|
+
ensureDir(path31.dirname(dest2));
|
|
24626
|
+
fs22.writeFileSync(dest2, toMarkdown(file), "utf-8");
|
|
23851
24627
|
}
|
|
23852
|
-
writeCanvas(
|
|
24628
|
+
writeCanvas(path31.join(outDir, "canvas.html"));
|
|
23853
24629
|
const exportModel = buildCanvasModel();
|
|
23854
|
-
|
|
23855
|
-
|
|
24630
|
+
fs22.writeFileSync(path31.join(outDir, "architecture.drawio"), generateDrawioXml(exportModel), "utf-8");
|
|
24631
|
+
fs22.writeFileSync(path31.join(outDir, "architecture.excalidraw"), generateExcalidrawScene(exportModel), "utf-8");
|
|
23856
24632
|
const graph = loadSpecGraph();
|
|
23857
|
-
const indexPath =
|
|
23858
|
-
|
|
24633
|
+
const indexPath = path31.join(outDir, "README.md");
|
|
24634
|
+
fs22.writeFileSync(indexPath, diagramSetIndex(files, graph.systemName), "utf-8");
|
|
23859
24635
|
logger.success(`Generated ${files.length} diagram(s) + interactive canvas.html + index into ${outDir}`);
|
|
23860
24636
|
for (const file of files.slice(0, 12)) {
|
|
23861
24637
|
logger.info(` ${file.relPath}`);
|
|
@@ -23866,26 +24642,26 @@ async function runDiagram(rawOptions = {}) {
|
|
|
23866
24642
|
let mermaid;
|
|
23867
24643
|
let title;
|
|
23868
24644
|
let defaultDest;
|
|
23869
|
-
const diagramsDir =
|
|
24645
|
+
const diagramsDir = path31.join(AI_PATHS.docsDir(), "diagrams");
|
|
23870
24646
|
if (options.sequence) {
|
|
23871
24647
|
const { component, method: method2 } = parseSequenceRef(options.sequence);
|
|
23872
24648
|
mermaid = generateSequenceDiagram(component, method2, { depth: options.depth });
|
|
23873
24649
|
title = `${component}.${method2} \u2014 narrative sequence`;
|
|
23874
|
-
defaultDest =
|
|
24650
|
+
defaultDest = path31.join(diagramsDir, "sequences", `${component.replace(/::/g, "--")}.${method2}.md`);
|
|
23875
24651
|
} else if (options.subsystem) {
|
|
23876
24652
|
mermaid = generateComponentDiagram({ subsystem: options.subsystem });
|
|
23877
24653
|
title = `${options.subsystem} \u2014 components`;
|
|
23878
|
-
defaultDest =
|
|
24654
|
+
defaultDest = path31.join(diagramsDir, "subsystems", `${options.subsystem.replace(/::/g, "--")}.md`);
|
|
23879
24655
|
} else {
|
|
23880
24656
|
mermaid = generateComponentDiagram();
|
|
23881
24657
|
title = "Component architecture";
|
|
23882
|
-
defaultDest =
|
|
24658
|
+
defaultDest = path31.join(diagramsDir, "system.md");
|
|
23883
24659
|
}
|
|
23884
24660
|
const dest = options.out ?? defaultDest;
|
|
23885
|
-
ensureDir(
|
|
24661
|
+
ensureDir(path31.dirname(path31.resolve(dest)));
|
|
23886
24662
|
const content = dest.endsWith(".mmd") ? `${mermaid}
|
|
23887
24663
|
` : toMarkdown({ relPath: dest, title, mermaid });
|
|
23888
|
-
|
|
24664
|
+
fs22.writeFileSync(dest, content, "utf-8");
|
|
23889
24665
|
logger.success(`Mermaid diagram written to ${dest}`);
|
|
23890
24666
|
logger.info("Renders on GitHub/IDE previews; use a .mmd --out path for raw Mermaid.");
|
|
23891
24667
|
}
|
|
@@ -23994,8 +24770,8 @@ Component variants (${variants.length})
|
|
|
23994
24770
|
}
|
|
23995
24771
|
|
|
23996
24772
|
// src/commands/packs.ts
|
|
23997
|
-
var
|
|
23998
|
-
var
|
|
24773
|
+
var fs23 = __toESM(require("fs"));
|
|
24774
|
+
var path32 = __toESM(require("path"));
|
|
23999
24775
|
var import_chalk16 = __toESM(require("chalk"));
|
|
24000
24776
|
var import_sdk = __toESM(require_dist());
|
|
24001
24777
|
init_logger();
|
|
@@ -24021,11 +24797,11 @@ function describe(probe2) {
|
|
|
24021
24797
|
return parts.join(", ");
|
|
24022
24798
|
}
|
|
24023
24799
|
function resolveSourceUnit(source) {
|
|
24024
|
-
const abs =
|
|
24025
|
-
if (!
|
|
24800
|
+
const abs = path32.resolve(source);
|
|
24801
|
+
if (!fs23.existsSync(abs)) {
|
|
24026
24802
|
throw new Error(`Pack source "${source}" does not exist.`);
|
|
24027
24803
|
}
|
|
24028
|
-
const isDir =
|
|
24804
|
+
const isDir = fs23.statSync(abs).isDirectory();
|
|
24029
24805
|
if (isDir && !packDirEntry(abs)) {
|
|
24030
24806
|
throw new Error(`"${source}" is a directory without a pack entry file (pack.yaml | pack.cjs | index.cjs | ...).`);
|
|
24031
24807
|
}
|
|
@@ -24041,7 +24817,7 @@ async function addPack(source, options = {}) {
|
|
|
24041
24817
|
}
|
|
24042
24818
|
const { abs } = resolveSourceUnit(source);
|
|
24043
24819
|
const scope = options.global ? "global" : "project";
|
|
24044
|
-
const probe2 = probePack(abs,
|
|
24820
|
+
const probe2 = probePack(abs, path32.dirname(abs), scope);
|
|
24045
24821
|
if (probe2.error) {
|
|
24046
24822
|
logger.error(probe2.error);
|
|
24047
24823
|
process.exitCode = 1;
|
|
@@ -24049,10 +24825,10 @@ async function addPack(source, options = {}) {
|
|
|
24049
24825
|
}
|
|
24050
24826
|
if (options.global) {
|
|
24051
24827
|
const destDir = globalPacksDir();
|
|
24052
|
-
const dest2 =
|
|
24053
|
-
if (
|
|
24054
|
-
|
|
24055
|
-
|
|
24828
|
+
const dest2 = path32.join(destDir, path32.basename(abs));
|
|
24829
|
+
if (path32.resolve(dest2) !== abs) {
|
|
24830
|
+
fs23.mkdirSync(destDir, { recursive: true });
|
|
24831
|
+
fs23.cpSync(abs, dest2, { recursive: true, force: true });
|
|
24056
24832
|
}
|
|
24057
24833
|
logger.success(`Installed pack "${probe2.name}" globally: ${dest2}`);
|
|
24058
24834
|
logger.info(`${describe(probe2)} \u2014 auto-loaded for every project on this machine (WAIRON_PACKS_DIR / ~/.wairon/packs).`);
|
|
@@ -24065,11 +24841,11 @@ async function addPack(source, options = {}) {
|
|
|
24065
24841
|
return;
|
|
24066
24842
|
}
|
|
24067
24843
|
const root = getProjectRoot();
|
|
24068
|
-
const relRef = `.wai/packs/${
|
|
24069
|
-
const dest =
|
|
24070
|
-
if (
|
|
24071
|
-
|
|
24072
|
-
|
|
24844
|
+
const relRef = `.wai/packs/${path32.basename(abs)}`;
|
|
24845
|
+
const dest = path32.join(root, ".wai", "packs", path32.basename(abs));
|
|
24846
|
+
if (path32.resolve(dest) !== abs) {
|
|
24847
|
+
fs23.mkdirSync(path32.dirname(dest), { recursive: true });
|
|
24848
|
+
fs23.cpSync(abs, dest, { recursive: true, force: true });
|
|
24073
24849
|
}
|
|
24074
24850
|
const config = loadProjectConfig();
|
|
24075
24851
|
const packs = config.extensions?.packs ?? [];
|
|
@@ -24078,14 +24854,14 @@ async function addPack(source, options = {}) {
|
|
|
24078
24854
|
saveProjectConfig(config);
|
|
24079
24855
|
logger.success(`Vendored pack "${probe2.name}" into ${relRef} and registered it in .wai/project.yaml.`);
|
|
24080
24856
|
} else {
|
|
24081
|
-
|
|
24857
|
+
fs23.cpSync(abs, dest, { recursive: true, force: true });
|
|
24082
24858
|
logger.success(`Pack "${probe2.name}" already registered \u2014 refreshed ${relRef} from the source.`);
|
|
24083
24859
|
}
|
|
24084
24860
|
logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
|
|
24085
24861
|
}
|
|
24086
24862
|
async function addPackFromArchive(source, options) {
|
|
24087
|
-
const abs =
|
|
24088
|
-
if (!
|
|
24863
|
+
const abs = path32.resolve(source);
|
|
24864
|
+
if (!fs23.existsSync(abs) || !fs23.statSync(abs).isFile()) {
|
|
24089
24865
|
logger.error(`Pack archive "${source}" does not exist.`);
|
|
24090
24866
|
process.exitCode = 1;
|
|
24091
24867
|
return;
|
|
@@ -24100,27 +24876,27 @@ async function addPackFromArchive(source, options) {
|
|
|
24100
24876
|
process.exitCode = 1;
|
|
24101
24877
|
return;
|
|
24102
24878
|
}
|
|
24103
|
-
baseDir =
|
|
24879
|
+
baseDir = path32.join(getProjectRoot(), ".wai", "packs");
|
|
24104
24880
|
}
|
|
24105
|
-
const bytes =
|
|
24106
|
-
|
|
24107
|
-
const staging =
|
|
24881
|
+
const bytes = fs23.readFileSync(abs);
|
|
24882
|
+
fs23.mkdirSync(baseDir, { recursive: true });
|
|
24883
|
+
const staging = fs23.mkdtempSync(path32.join(baseDir, ".wpack-staging-"));
|
|
24108
24884
|
let result;
|
|
24109
24885
|
try {
|
|
24110
24886
|
result = (0, import_sdk.extractPack)(bytes, staging);
|
|
24111
24887
|
} catch (err) {
|
|
24112
|
-
|
|
24113
|
-
logger.error(`Failed to extract pack archive "${
|
|
24888
|
+
fs23.rmSync(staging, { recursive: true, force: true });
|
|
24889
|
+
logger.error(`Failed to extract pack archive "${path32.basename(abs)}": ${err instanceof Error ? err.message : String(err)}`);
|
|
24114
24890
|
process.exitCode = 1;
|
|
24115
24891
|
return;
|
|
24116
24892
|
}
|
|
24117
24893
|
const name = result.name;
|
|
24118
|
-
const destDir =
|
|
24119
|
-
if (
|
|
24120
|
-
|
|
24121
|
-
const probe2 = probePack(destDir,
|
|
24894
|
+
const destDir = path32.join(baseDir, name);
|
|
24895
|
+
if (fs23.existsSync(destDir)) fs23.rmSync(destDir, { recursive: true, force: true });
|
|
24896
|
+
fs23.renameSync(staging, destDir);
|
|
24897
|
+
const probe2 = probePack(destDir, path32.dirname(destDir), scope);
|
|
24122
24898
|
if (probe2.error) {
|
|
24123
|
-
|
|
24899
|
+
fs23.rmSync(destDir, { recursive: true, force: true });
|
|
24124
24900
|
logger.error(probe2.error);
|
|
24125
24901
|
process.exitCode = 1;
|
|
24126
24902
|
return;
|
|
@@ -24138,7 +24914,7 @@ async function addPackFromArchive(source, options) {
|
|
|
24138
24914
|
saveProjectConfig(config);
|
|
24139
24915
|
logger.success(`Installed pack "${probe2.name ?? name}" into ${relRef} and registered it in .wai/project.yaml.`);
|
|
24140
24916
|
} else {
|
|
24141
|
-
logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${
|
|
24917
|
+
logger.success(`Pack "${probe2.name ?? name}" already registered \u2014 refreshed ${relRef} from ${path32.basename(abs)}.`);
|
|
24142
24918
|
}
|
|
24143
24919
|
logger.info(`${describe(probe2)} \u2014 commit .wai/ so CI and every clone enforce it.`);
|
|
24144
24920
|
}
|
|
@@ -24159,7 +24935,7 @@ async function buildPack(source, options = {}) {
|
|
|
24159
24935
|
const sourceDir = source && source.length > 0 ? source : ".";
|
|
24160
24936
|
const result = (0, import_sdk.buildPack)(sourceDir);
|
|
24161
24937
|
const outPath = options.out ?? result.suggestedFileName;
|
|
24162
|
-
|
|
24938
|
+
fs23.writeFileSync(outPath, result.archive);
|
|
24163
24939
|
logger.success(`Built pack "${result.info.name}" v${result.info.version} \u2192 ${outPath} (${result.archive.byteLength} bytes)`);
|
|
24164
24940
|
logger.info(`Install it with \`wairon pack add ${outPath}\`, or upload it to a hosted instance.`);
|
|
24165
24941
|
}
|
|
@@ -24173,9 +24949,9 @@ async function listPacks() {
|
|
|
24173
24949
|
console.log(import_chalk16.default.bold.cyan(`\u25A0 Global (${globalPacksDir()})${useGlobal ? "" : import_chalk16.default.yellow(" [disabled: extensions.useGlobalPacks: false]")}`));
|
|
24174
24950
|
if (globalRefs.length === 0) console.log(import_chalk16.default.dim(" (none)"));
|
|
24175
24951
|
for (const ref of globalRefs) {
|
|
24176
|
-
const probe2 = probePack(ref,
|
|
24177
|
-
if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${
|
|
24178
|
-
else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ??
|
|
24952
|
+
const probe2 = probePack(ref, path32.dirname(ref), "global");
|
|
24953
|
+
if (probe2.error) console.log(` ${import_chalk16.default.red("\u2716")} ${path32.basename(ref)} \u2014 ${import_chalk16.default.red(probe2.error)}`);
|
|
24954
|
+
else console.log(` ${import_chalk16.default.green("\u25CF")} ${import_chalk16.default.bold(probe2.name ?? path32.basename(ref))} ${import_chalk16.default.dim(describe(probe2))}`);
|
|
24179
24955
|
}
|
|
24180
24956
|
console.log("");
|
|
24181
24957
|
if (!inProject) {
|
|
@@ -24196,9 +24972,9 @@ async function listPacks() {
|
|
|
24196
24972
|
async function removePack(name, options = {}) {
|
|
24197
24973
|
if (options.global) {
|
|
24198
24974
|
for (const ref of discoverPacks(globalPacksDir())) {
|
|
24199
|
-
const probe2 = probePack(ref,
|
|
24200
|
-
if (probe2.name === name ||
|
|
24201
|
-
|
|
24975
|
+
const probe2 = probePack(ref, path32.dirname(ref), "global");
|
|
24976
|
+
if (probe2.name === name || path32.basename(ref) === name) {
|
|
24977
|
+
fs23.rmSync(ref, { recursive: true, force: true });
|
|
24202
24978
|
logger.success(`Removed global pack "${probe2.name ?? name}" (${ref}).`);
|
|
24203
24979
|
return;
|
|
24204
24980
|
}
|
|
@@ -24217,16 +24993,16 @@ async function removePack(name, options = {}) {
|
|
|
24217
24993
|
const packs = config.extensions?.packs ?? [];
|
|
24218
24994
|
for (const ref of packs) {
|
|
24219
24995
|
const probe2 = probePack(ref, root, "project");
|
|
24220
|
-
if (probe2.name === name || ref === name ||
|
|
24996
|
+
if (probe2.name === name || ref === name || path32.basename(ref) === name) {
|
|
24221
24997
|
config.extensions = {
|
|
24222
24998
|
packs: packs.filter((p) => p !== ref),
|
|
24223
24999
|
useGlobalPacks: config.extensions?.useGlobalPacks ?? true
|
|
24224
25000
|
};
|
|
24225
25001
|
saveProjectConfig(config);
|
|
24226
|
-
const resolved =
|
|
24227
|
-
const vendorDir =
|
|
24228
|
-
if (resolved.startsWith(vendorDir +
|
|
24229
|
-
|
|
25002
|
+
const resolved = path32.resolve(root, ref);
|
|
25003
|
+
const vendorDir = path32.resolve(root, ".wai", "packs");
|
|
25004
|
+
if (resolved.startsWith(vendorDir + path32.sep)) {
|
|
25005
|
+
fs23.rmSync(resolved, { recursive: true, force: true });
|
|
24230
25006
|
logger.success(`Deregistered pack "${probe2.name ?? name}" and deleted ${ref}.`);
|
|
24231
25007
|
} else {
|
|
24232
25008
|
logger.success(`Deregistered pack "${probe2.name ?? name}" (files at ${ref} left in place).`);
|
|
@@ -24240,8 +25016,8 @@ async function removePack(name, options = {}) {
|
|
|
24240
25016
|
|
|
24241
25017
|
// src/commands/host.ts
|
|
24242
25018
|
var fs50 = __toESM(require("fs"));
|
|
24243
|
-
var
|
|
24244
|
-
var
|
|
25019
|
+
var path59 = __toESM(require("path"));
|
|
25020
|
+
var os10 = __toESM(require("os"));
|
|
24245
25021
|
var crypto20 = __toESM(require("crypto"));
|
|
24246
25022
|
var import_child_process5 = require("child_process");
|
|
24247
25023
|
var import_chalk17 = __toESM(require("chalk"));
|
|
@@ -24268,29 +25044,29 @@ var UNAUTHENTICATED = {
|
|
|
24268
25044
|
var WEB_SESSION_PREFIX = "ws_";
|
|
24269
25045
|
|
|
24270
25046
|
// src/server/credentials.ts
|
|
24271
|
-
var
|
|
24272
|
-
var
|
|
25047
|
+
var fs24 = __toESM(require("fs"));
|
|
25048
|
+
var path33 = __toESM(require("path"));
|
|
24273
25049
|
var crypto3 = __toESM(require("crypto"));
|
|
24274
25050
|
var HASH_NS = "wairon:token:v1";
|
|
24275
25051
|
function hashToken(token) {
|
|
24276
25052
|
return crypto3.createHash("sha256").update(`${HASH_NS}:${token}`).digest("hex");
|
|
24277
25053
|
}
|
|
24278
25054
|
function storePath(dataDir) {
|
|
24279
|
-
return
|
|
25055
|
+
return path33.join(dataDir, "auth", "credentials.json");
|
|
24280
25056
|
}
|
|
24281
25057
|
function load3(dataDir) {
|
|
24282
25058
|
try {
|
|
24283
|
-
return JSON.parse(
|
|
25059
|
+
return JSON.parse(fs24.readFileSync(storePath(dataDir), "utf8"));
|
|
24284
25060
|
} catch {
|
|
24285
25061
|
return [];
|
|
24286
25062
|
}
|
|
24287
25063
|
}
|
|
24288
25064
|
function save(dataDir, records) {
|
|
24289
25065
|
const p = storePath(dataDir);
|
|
24290
|
-
|
|
25066
|
+
fs24.mkdirSync(path33.dirname(p), { recursive: true });
|
|
24291
25067
|
const tmp = `${p}.tmp`;
|
|
24292
|
-
|
|
24293
|
-
|
|
25068
|
+
fs24.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
25069
|
+
fs24.renameSync(tmp, p);
|
|
24294
25070
|
}
|
|
24295
25071
|
function digestEquals(a, b) {
|
|
24296
25072
|
const ab = Buffer.from(a, "hex");
|
|
@@ -24335,17 +25111,17 @@ function listByOwner(dataDir, ownerUserId) {
|
|
|
24335
25111
|
}
|
|
24336
25112
|
|
|
24337
25113
|
// src/server/websessions.ts
|
|
24338
|
-
var
|
|
24339
|
-
var
|
|
25114
|
+
var fs25 = __toESM(require("fs"));
|
|
25115
|
+
var path34 = __toESM(require("path"));
|
|
24340
25116
|
var crypto4 = __toESM(require("crypto"));
|
|
24341
25117
|
function storePath2(dataDir) {
|
|
24342
|
-
return
|
|
25118
|
+
return path34.join(dataDir, "web-sessions.json");
|
|
24343
25119
|
}
|
|
24344
25120
|
function readSessions(dataDir) {
|
|
24345
25121
|
const p = storePath2(dataDir);
|
|
24346
25122
|
let raw;
|
|
24347
25123
|
try {
|
|
24348
|
-
raw =
|
|
25124
|
+
raw = fs25.readFileSync(p, "utf8");
|
|
24349
25125
|
} catch (e) {
|
|
24350
25126
|
if (e.code === "ENOENT") return [];
|
|
24351
25127
|
throw new Error(`Failed to read web session store at ${p}: ${e.message}`);
|
|
@@ -24360,10 +25136,10 @@ function readSessions(dataDir) {
|
|
|
24360
25136
|
}
|
|
24361
25137
|
function persistSessions(dataDir, sessions) {
|
|
24362
25138
|
const p = storePath2(dataDir);
|
|
24363
|
-
|
|
25139
|
+
fs25.mkdirSync(path34.dirname(p), { recursive: true });
|
|
24364
25140
|
const tmp = `${p}.tmp`;
|
|
24365
|
-
|
|
24366
|
-
|
|
25141
|
+
fs25.writeFileSync(tmp, JSON.stringify(sessions, null, 2) + "\n");
|
|
25142
|
+
fs25.renameSync(tmp, p);
|
|
24367
25143
|
}
|
|
24368
25144
|
function mintSessionId() {
|
|
24369
25145
|
return `${WEB_SESSION_PREFIX}${crypto4.randomBytes(24).toString("hex")}`;
|
|
@@ -24524,17 +25300,17 @@ function listWebSessionsBySubject(dataDir, userId) {
|
|
|
24524
25300
|
}
|
|
24525
25301
|
|
|
24526
25302
|
// src/server/users.ts
|
|
24527
|
-
var
|
|
24528
|
-
var
|
|
25303
|
+
var fs26 = __toESM(require("fs"));
|
|
25304
|
+
var path35 = __toESM(require("path"));
|
|
24529
25305
|
var VALID_STATUSES = ["active", "inactive", "suspended", "deactivated", "disabled"];
|
|
24530
25306
|
function storePath3(dataDir) {
|
|
24531
|
-
return
|
|
25307
|
+
return path35.join(dataDir, "users.json");
|
|
24532
25308
|
}
|
|
24533
25309
|
function loadStore(dataDir) {
|
|
24534
25310
|
const p = storePath3(dataDir);
|
|
24535
25311
|
let raw;
|
|
24536
25312
|
try {
|
|
24537
|
-
raw =
|
|
25313
|
+
raw = fs26.readFileSync(p, "utf8");
|
|
24538
25314
|
} catch (err) {
|
|
24539
25315
|
if (err.code === "ENOENT") return [];
|
|
24540
25316
|
throw new Error(`Cannot read hosted-user store at ${p}: ${err.message}`);
|
|
@@ -24552,10 +25328,10 @@ function loadStore(dataDir) {
|
|
|
24552
25328
|
}
|
|
24553
25329
|
function replaceAll(dataDir, records) {
|
|
24554
25330
|
const p = storePath3(dataDir);
|
|
24555
|
-
|
|
25331
|
+
fs26.mkdirSync(path35.dirname(p), { recursive: true });
|
|
24556
25332
|
const tmp = `${p}.tmp`;
|
|
24557
|
-
|
|
24558
|
-
|
|
25333
|
+
fs26.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
25334
|
+
fs26.renameSync(tmp, p);
|
|
24559
25335
|
}
|
|
24560
25336
|
function registryUpsert(dataDir, record2) {
|
|
24561
25337
|
const records = loadStore(dataDir);
|
|
@@ -24657,11 +25433,11 @@ function remapUnitReferences(dataDir, remap, removedScopeIds) {
|
|
|
24657
25433
|
}
|
|
24658
25434
|
|
|
24659
25435
|
// src/server/instance.ts
|
|
24660
|
-
var
|
|
24661
|
-
var
|
|
25436
|
+
var fs27 = __toESM(require("fs"));
|
|
25437
|
+
var path36 = __toESM(require("path"));
|
|
24662
25438
|
var import_crypto = require("crypto");
|
|
24663
25439
|
function storePath4(dataDir) {
|
|
24664
|
-
return
|
|
25440
|
+
return path36.join(dataDir, "instance.json");
|
|
24665
25441
|
}
|
|
24666
25442
|
var InstanceIdentityStore = class {
|
|
24667
25443
|
constructor(dataDir) {
|
|
@@ -24678,7 +25454,7 @@ var InstanceIdentityStore = class {
|
|
|
24678
25454
|
const p = storePath4(this.dataDir);
|
|
24679
25455
|
let raw;
|
|
24680
25456
|
try {
|
|
24681
|
-
raw =
|
|
25457
|
+
raw = fs27.readFileSync(p, "utf8");
|
|
24682
25458
|
} catch (err) {
|
|
24683
25459
|
if (err.code === "ENOENT") return null;
|
|
24684
25460
|
throw new Error(`Cannot read instance identity at ${p}: ${err.message}`);
|
|
@@ -24701,10 +25477,10 @@ var InstanceIdentityStore = class {
|
|
|
24701
25477
|
* never truncates the file. Only called by the registry's create-once seed. */
|
|
24702
25478
|
replace(identity) {
|
|
24703
25479
|
const p = storePath4(this.dataDir);
|
|
24704
|
-
|
|
25480
|
+
fs27.mkdirSync(path36.dirname(p), { recursive: true });
|
|
24705
25481
|
const tmp = `${p}.tmp`;
|
|
24706
|
-
|
|
24707
|
-
|
|
25482
|
+
fs27.writeFileSync(tmp, JSON.stringify(identity, null, 2) + "\n");
|
|
25483
|
+
fs27.renameSync(tmp, p);
|
|
24708
25484
|
}
|
|
24709
25485
|
};
|
|
24710
25486
|
var InstanceIdentityRegistry = class {
|
|
@@ -24760,8 +25536,8 @@ function getInstanceIdentity(dataDir) {
|
|
|
24760
25536
|
}
|
|
24761
25537
|
|
|
24762
25538
|
// src/utils/secrets.ts
|
|
24763
|
-
var
|
|
24764
|
-
var
|
|
25539
|
+
var fs28 = __toESM(require("fs"));
|
|
25540
|
+
var path37 = __toESM(require("path"));
|
|
24765
25541
|
var ENV_FALLBACK = {
|
|
24766
25542
|
"git-token": ["WAIRON_GIT_TOKEN"],
|
|
24767
25543
|
"notion-token": ["WAIRON_NOTION_TOKEN"],
|
|
@@ -24770,13 +25546,13 @@ var ENV_FALLBACK = {
|
|
|
24770
25546
|
};
|
|
24771
25547
|
function storePath5() {
|
|
24772
25548
|
const dataDir = process.env["WAIRON_DATA_DIR"];
|
|
24773
|
-
return dataDir ?
|
|
25549
|
+
return dataDir ? path37.join(dataDir, "auth", "secrets.json") : null;
|
|
24774
25550
|
}
|
|
24775
25551
|
function readStore() {
|
|
24776
25552
|
const p = storePath5();
|
|
24777
25553
|
if (!p) return {};
|
|
24778
25554
|
try {
|
|
24779
|
-
return JSON.parse(
|
|
25555
|
+
return JSON.parse(fs28.readFileSync(p, "utf8"));
|
|
24780
25556
|
} catch {
|
|
24781
25557
|
return {};
|
|
24782
25558
|
}
|
|
@@ -24801,10 +25577,10 @@ function setSecret(key, value) {
|
|
|
24801
25577
|
if (!p) throw new Error("WAIRON_DATA_DIR is not set \u2014 a running server needs it to store secrets.");
|
|
24802
25578
|
const store = readStore();
|
|
24803
25579
|
store[key] = value;
|
|
24804
|
-
|
|
25580
|
+
fs28.mkdirSync(path37.dirname(p), { recursive: true });
|
|
24805
25581
|
const tmp = `${p}.tmp`;
|
|
24806
|
-
|
|
24807
|
-
|
|
25582
|
+
fs28.writeFileSync(tmp, JSON.stringify(store, null, 2) + "\n");
|
|
25583
|
+
fs28.renameSync(tmp, p);
|
|
24808
25584
|
}
|
|
24809
25585
|
function listSecretKeys() {
|
|
24810
25586
|
return Object.keys(readStore());
|
|
@@ -25012,17 +25788,17 @@ function verifySsoState(state) {
|
|
|
25012
25788
|
}
|
|
25013
25789
|
|
|
25014
25790
|
// src/server/organization.ts
|
|
25015
|
-
var
|
|
25016
|
-
var
|
|
25791
|
+
var fs29 = __toESM(require("fs"));
|
|
25792
|
+
var path38 = __toESM(require("path"));
|
|
25017
25793
|
var crypto6 = __toESM(require("crypto"));
|
|
25018
25794
|
function storePath6(dataDir) {
|
|
25019
|
-
return
|
|
25795
|
+
return path38.join(dataDir, "organization.json");
|
|
25020
25796
|
}
|
|
25021
25797
|
function readState(dataDir) {
|
|
25022
25798
|
const p = storePath6(dataDir);
|
|
25023
25799
|
let raw;
|
|
25024
25800
|
try {
|
|
25025
|
-
raw =
|
|
25801
|
+
raw = fs29.readFileSync(p, "utf8");
|
|
25026
25802
|
} catch (e) {
|
|
25027
25803
|
if (e.code === "ENOENT") return { units: [], placements: [] };
|
|
25028
25804
|
throw new Error(`Failed to read organization store at ${p}: ${e.message}`);
|
|
@@ -25039,10 +25815,10 @@ function readState(dataDir) {
|
|
|
25039
25815
|
}
|
|
25040
25816
|
function persistState(dataDir, state) {
|
|
25041
25817
|
const p = storePath6(dataDir);
|
|
25042
|
-
|
|
25818
|
+
fs29.mkdirSync(path38.dirname(p), { recursive: true });
|
|
25043
25819
|
const tmp = `${p}.tmp`;
|
|
25044
|
-
|
|
25045
|
-
|
|
25820
|
+
fs29.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
|
|
25821
|
+
fs29.renameSync(tmp, p);
|
|
25046
25822
|
}
|
|
25047
25823
|
var SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
25048
25824
|
var UNIT_KINDS = ["business_entity", "department", "team", "group"];
|
|
@@ -25411,11 +26187,11 @@ function getOrganizationUnit(dataDir, id) {
|
|
|
25411
26187
|
}
|
|
25412
26188
|
|
|
25413
26189
|
// src/server/permissions.ts
|
|
25414
|
-
var
|
|
25415
|
-
var
|
|
26190
|
+
var fs30 = __toESM(require("fs"));
|
|
26191
|
+
var path39 = __toESM(require("path"));
|
|
25416
26192
|
var import_crypto2 = require("crypto");
|
|
25417
26193
|
function storePath7(dataDir) {
|
|
25418
|
-
return
|
|
26194
|
+
return path39.join(dataDir, "permissions.json");
|
|
25419
26195
|
}
|
|
25420
26196
|
function assignmentKey(a) {
|
|
25421
26197
|
return [a.subjectKind, a.subjectId ?? "", a.scopeKind, a.scopeId ?? "", a.capability].join("|");
|
|
@@ -25424,7 +26200,7 @@ function load4(dataDir) {
|
|
|
25424
26200
|
const p = storePath7(dataDir);
|
|
25425
26201
|
let raw;
|
|
25426
26202
|
try {
|
|
25427
|
-
raw =
|
|
26203
|
+
raw = fs30.readFileSync(p, "utf8");
|
|
25428
26204
|
} catch (err) {
|
|
25429
26205
|
if (err.code === "ENOENT") return [];
|
|
25430
26206
|
throw new Error(`Cannot read permission store at ${p}: ${err.message}`);
|
|
@@ -25442,10 +26218,10 @@ function load4(dataDir) {
|
|
|
25442
26218
|
}
|
|
25443
26219
|
function replaceAll2(dataDir, assignments) {
|
|
25444
26220
|
const p = storePath7(dataDir);
|
|
25445
|
-
|
|
26221
|
+
fs30.mkdirSync(path39.dirname(p), { recursive: true });
|
|
25446
26222
|
const tmp = `${p}.tmp`;
|
|
25447
|
-
|
|
25448
|
-
|
|
26223
|
+
fs30.writeFileSync(tmp, JSON.stringify(assignments, null, 2) + "\n");
|
|
26224
|
+
fs30.renameSync(tmp, p);
|
|
25449
26225
|
}
|
|
25450
26226
|
function registrySet(dataDir, assignment) {
|
|
25451
26227
|
const assignments = load4(dataDir);
|
|
@@ -25531,8 +26307,8 @@ function getAssignment(dataDir, assignmentId) {
|
|
|
25531
26307
|
}
|
|
25532
26308
|
|
|
25533
26309
|
// src/server/roles.ts
|
|
25534
|
-
var
|
|
25535
|
-
var
|
|
26310
|
+
var fs31 = __toESM(require("fs"));
|
|
26311
|
+
var path40 = __toESM(require("path"));
|
|
25536
26312
|
var BUILTIN_ROLES = [
|
|
25537
26313
|
{
|
|
25538
26314
|
id: SSO_ADMIN_ROLE_ID,
|
|
@@ -25550,13 +26326,13 @@ function isBuiltinRoleId(roleId) {
|
|
|
25550
26326
|
return BUILTIN_ROLE_IDS.has(roleId);
|
|
25551
26327
|
}
|
|
25552
26328
|
function storePath8(dataDir) {
|
|
25553
|
-
return
|
|
26329
|
+
return path40.join(dataDir, "roles.json");
|
|
25554
26330
|
}
|
|
25555
26331
|
function load5(dataDir) {
|
|
25556
26332
|
const p = storePath8(dataDir);
|
|
25557
26333
|
let raw;
|
|
25558
26334
|
try {
|
|
25559
|
-
raw =
|
|
26335
|
+
raw = fs31.readFileSync(p, "utf8");
|
|
25560
26336
|
} catch (err) {
|
|
25561
26337
|
if (err.code === "ENOENT") return [];
|
|
25562
26338
|
throw new Error(`Cannot read role store at ${p}: ${err.message}`);
|
|
@@ -25574,10 +26350,10 @@ function load5(dataDir) {
|
|
|
25574
26350
|
}
|
|
25575
26351
|
function replaceAll3(dataDir, roles) {
|
|
25576
26352
|
const p = storePath8(dataDir);
|
|
25577
|
-
|
|
26353
|
+
fs31.mkdirSync(path40.dirname(p), { recursive: true });
|
|
25578
26354
|
const tmp = `${p}.tmp`;
|
|
25579
|
-
|
|
25580
|
-
|
|
26355
|
+
fs31.writeFileSync(tmp, JSON.stringify(roles, null, 2) + "\n");
|
|
26356
|
+
fs31.renameSync(tmp, p);
|
|
25581
26357
|
}
|
|
25582
26358
|
function registryCreate(dataDir, role) {
|
|
25583
26359
|
if (isBuiltinRoleId(role.id)) {
|
|
@@ -25811,131 +26587,14 @@ function actionableUnitIds(scopes) {
|
|
|
25811
26587
|
}
|
|
25812
26588
|
|
|
25813
26589
|
// src/server/projects.ts
|
|
25814
|
-
var
|
|
25815
|
-
var
|
|
25816
|
-
|
|
25817
|
-
|
|
25818
|
-
return typeof id === "string" && ID_RE.test(id);
|
|
25819
|
-
}
|
|
25820
|
-
function registryPath(dataDir) {
|
|
25821
|
-
return path40.join(dataDir, "projects.json");
|
|
25822
|
-
}
|
|
25823
|
-
function load6(dataDir) {
|
|
25824
|
-
try {
|
|
25825
|
-
return JSON.parse(fs31.readFileSync(registryPath(dataDir), "utf8"));
|
|
25826
|
-
} catch {
|
|
25827
|
-
return [];
|
|
25828
|
-
}
|
|
25829
|
-
}
|
|
25830
|
-
function save2(dataDir, records) {
|
|
25831
|
-
const p = registryPath(dataDir);
|
|
25832
|
-
fs31.mkdirSync(path40.dirname(p), { recursive: true });
|
|
25833
|
-
const tmp = `${p}.tmp`;
|
|
25834
|
-
fs31.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
25835
|
-
fs31.renameSync(tmp, p);
|
|
25836
|
-
}
|
|
25837
|
-
function projectRoot(dataDir, id) {
|
|
25838
|
-
return path40.join(dataDir, "projects", id);
|
|
25839
|
-
}
|
|
25840
|
-
function existingProjectRoot(dataDir, id) {
|
|
25841
|
-
if (!isValidProjectId(id)) return null;
|
|
25842
|
-
const rec = load6(dataDir).find((r) => r.id === id);
|
|
25843
|
-
return rec ? rec.rootPath : null;
|
|
25844
|
-
}
|
|
25845
|
-
function createProjectRecord(dataDir, id) {
|
|
25846
|
-
if (!isValidProjectId(id)) {
|
|
25847
|
-
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
25848
|
-
}
|
|
25849
|
-
const records = load6(dataDir);
|
|
25850
|
-
if (records.some((r) => r.id === id)) {
|
|
25851
|
-
throw new Error(`Project "${id}" already exists.`);
|
|
25852
|
-
}
|
|
25853
|
-
const root = projectRoot(dataDir, id);
|
|
25854
|
-
fs31.mkdirSync(root, { recursive: true });
|
|
25855
|
-
const record2 = {
|
|
25856
|
-
id,
|
|
25857
|
-
rootPath: root,
|
|
25858
|
-
status: "active",
|
|
25859
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
25860
|
-
};
|
|
25861
|
-
records.push(record2);
|
|
25862
|
-
save2(dataDir, records);
|
|
25863
|
-
return record2;
|
|
25864
|
-
}
|
|
25865
|
-
function registerLocalDevProject(dataDir, id, rootPath) {
|
|
25866
|
-
if (!isValidProjectId(id)) {
|
|
25867
|
-
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
25868
|
-
}
|
|
25869
|
-
const records = load6(dataDir);
|
|
25870
|
-
const existing = records.find((r) => r.id === id);
|
|
25871
|
-
const record2 = {
|
|
25872
|
-
id,
|
|
25873
|
-
rootPath,
|
|
25874
|
-
status: "active",
|
|
25875
|
-
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
25876
|
-
};
|
|
25877
|
-
const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
|
|
25878
|
-
save2(dataDir, next);
|
|
25879
|
-
return record2;
|
|
25880
|
-
}
|
|
25881
|
-
function listProjectRecords(dataDir) {
|
|
25882
|
-
return load6(dataDir);
|
|
25883
|
-
}
|
|
25884
|
-
function removeProjectRecord(dataDir, id) {
|
|
25885
|
-
const records = load6(dataDir);
|
|
25886
|
-
const rec = records.find((r) => r.id === id);
|
|
25887
|
-
if (rec) {
|
|
25888
|
-
try {
|
|
25889
|
-
fs31.rmSync(rec.rootPath, { recursive: true, force: true });
|
|
25890
|
-
} catch {
|
|
25891
|
-
}
|
|
25892
|
-
}
|
|
25893
|
-
save2(dataDir, records.filter((r) => r.id !== id));
|
|
25894
|
-
}
|
|
25895
|
-
function resolveProjectRoot(dataDir, principal, selector) {
|
|
25896
|
-
const authorized = principal.projects;
|
|
25897
|
-
const wildcard = authorized.includes("*");
|
|
25898
|
-
let target;
|
|
25899
|
-
if (selector) {
|
|
25900
|
-
if (!wildcard && !authorized.includes(selector)) return null;
|
|
25901
|
-
target = selector;
|
|
25902
|
-
} else if (!wildcard && authorized.length === 1) {
|
|
25903
|
-
target = authorized[0];
|
|
25904
|
-
} else {
|
|
25905
|
-
return null;
|
|
25906
|
-
}
|
|
25907
|
-
if (!isValidProjectId(target)) return null;
|
|
25908
|
-
const rec = load6(dataDir).find((r) => r.id === target);
|
|
25909
|
-
if (!rec || rec.status !== "active") return null;
|
|
25910
|
-
return rec.rootPath;
|
|
25911
|
-
}
|
|
25912
|
-
|
|
25913
|
-
// src/server/adapters.ts
|
|
25914
|
-
init_statehash();
|
|
25915
|
-
|
|
25916
|
-
// src/core/lockfile.ts
|
|
25917
|
-
var fs32 = __toESM(require("fs"));
|
|
25918
|
-
var path41 = __toESM(require("path"));
|
|
26590
|
+
var fs35 = __toESM(require("fs"));
|
|
26591
|
+
var path44 = __toESM(require("path"));
|
|
26592
|
+
init_loader();
|
|
26593
|
+
init_yaml();
|
|
25919
26594
|
init_fs();
|
|
25920
|
-
function lockPath() {
|
|
25921
|
-
return aiDir("lock.json");
|
|
25922
|
-
}
|
|
25923
|
-
function readLockRecord() {
|
|
25924
|
-
try {
|
|
25925
|
-
return JSON.parse(fs32.readFileSync(lockPath(), "utf8"));
|
|
25926
|
-
} catch {
|
|
25927
|
-
return null;
|
|
25928
|
-
}
|
|
25929
|
-
}
|
|
25930
|
-
function writeLockRecord(record2) {
|
|
25931
|
-
const p = lockPath();
|
|
25932
|
-
fs32.mkdirSync(path41.dirname(p), { recursive: true });
|
|
25933
|
-
const tmp = `${p}.tmp`;
|
|
25934
|
-
fs32.writeFileSync(tmp, JSON.stringify(record2, null, 2) + "\n");
|
|
25935
|
-
fs32.renameSync(tmp, p);
|
|
25936
|
-
}
|
|
25937
26595
|
|
|
25938
26596
|
// src/server/adapters.ts
|
|
26597
|
+
init_statehash();
|
|
25939
26598
|
init_specs2();
|
|
25940
26599
|
init_provision();
|
|
25941
26600
|
init_validation();
|
|
@@ -25946,37 +26605,37 @@ init_types();
|
|
|
25946
26605
|
init_server();
|
|
25947
26606
|
|
|
25948
26607
|
// src/git/config.ts
|
|
25949
|
-
var
|
|
25950
|
-
var
|
|
26608
|
+
var fs32 = __toESM(require("fs"));
|
|
26609
|
+
var path41 = __toESM(require("path"));
|
|
25951
26610
|
init_fs();
|
|
25952
26611
|
function configPath() {
|
|
25953
26612
|
return aiDir("git.json");
|
|
25954
26613
|
}
|
|
25955
26614
|
function readGitConfig() {
|
|
25956
26615
|
try {
|
|
25957
|
-
return JSON.parse(
|
|
26616
|
+
return JSON.parse(fs32.readFileSync(configPath(), "utf8"));
|
|
25958
26617
|
} catch {
|
|
25959
26618
|
return null;
|
|
25960
26619
|
}
|
|
25961
26620
|
}
|
|
25962
26621
|
function writeGitConfig(config) {
|
|
25963
26622
|
const p = configPath();
|
|
25964
|
-
|
|
26623
|
+
fs32.mkdirSync(path41.dirname(p), { recursive: true });
|
|
25965
26624
|
const tmp = `${p}.tmp`;
|
|
25966
|
-
|
|
25967
|
-
|
|
26625
|
+
fs32.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n");
|
|
26626
|
+
fs32.renameSync(tmp, p);
|
|
25968
26627
|
}
|
|
25969
26628
|
function clearGitConfig() {
|
|
25970
26629
|
try {
|
|
25971
|
-
|
|
26630
|
+
fs32.rmSync(configPath(), { force: true });
|
|
25972
26631
|
} catch {
|
|
25973
26632
|
}
|
|
25974
26633
|
}
|
|
25975
26634
|
|
|
25976
26635
|
// src/git/adapter.ts
|
|
25977
26636
|
var import_child_process3 = require("child_process");
|
|
25978
|
-
var
|
|
25979
|
-
var
|
|
26637
|
+
var fs33 = __toESM(require("fs"));
|
|
26638
|
+
var path42 = __toESM(require("path"));
|
|
25980
26639
|
init_fs();
|
|
25981
26640
|
function git(args, cwd) {
|
|
25982
26641
|
return (0, import_child_process3.execFileSync)("git", args, {
|
|
@@ -26028,10 +26687,10 @@ function compareUrl(remote, defaultBranch, workingBranch) {
|
|
|
26028
26687
|
return `${web}/compare/${encodeURIComponent(defaultBranch)}...${encodeURIComponent(workingBranch)}`;
|
|
26029
26688
|
}
|
|
26030
26689
|
function excludeLocalFiles() {
|
|
26031
|
-
const excludePath =
|
|
26690
|
+
const excludePath = path42.join(getProjectRoot(), ".git", "info", "exclude");
|
|
26032
26691
|
try {
|
|
26033
|
-
|
|
26034
|
-
|
|
26692
|
+
fs33.mkdirSync(path42.dirname(excludePath), { recursive: true });
|
|
26693
|
+
fs33.appendFileSync(excludePath, "\n.wai/lock.json\n.wai/git.json\n");
|
|
26035
26694
|
} catch {
|
|
26036
26695
|
}
|
|
26037
26696
|
}
|
|
@@ -26096,39 +26755,39 @@ function configureSync(periodicSyncMinutes, skipIfClean) {
|
|
|
26096
26755
|
}
|
|
26097
26756
|
|
|
26098
26757
|
// src/producers/config.ts
|
|
26099
|
-
var
|
|
26100
|
-
var
|
|
26758
|
+
var fs34 = __toESM(require("fs"));
|
|
26759
|
+
var path43 = __toESM(require("path"));
|
|
26101
26760
|
init_fs();
|
|
26102
26761
|
function configPath2() {
|
|
26103
26762
|
return aiDir("producers.json");
|
|
26104
26763
|
}
|
|
26105
|
-
function
|
|
26764
|
+
function load6() {
|
|
26106
26765
|
try {
|
|
26107
|
-
return JSON.parse(
|
|
26766
|
+
return JSON.parse(fs34.readFileSync(configPath2(), "utf8"));
|
|
26108
26767
|
} catch {
|
|
26109
26768
|
return [];
|
|
26110
26769
|
}
|
|
26111
26770
|
}
|
|
26112
|
-
function
|
|
26771
|
+
function save2(configs) {
|
|
26113
26772
|
const p = configPath2();
|
|
26114
|
-
|
|
26773
|
+
fs34.mkdirSync(path43.dirname(p), { recursive: true });
|
|
26115
26774
|
const tmp = `${p}.tmp`;
|
|
26116
|
-
|
|
26117
|
-
|
|
26775
|
+
fs34.writeFileSync(tmp, JSON.stringify(configs, null, 2) + "\n");
|
|
26776
|
+
fs34.renameSync(tmp, p);
|
|
26118
26777
|
}
|
|
26119
26778
|
function readProducerConfig(target) {
|
|
26120
|
-
return
|
|
26779
|
+
return load6().find((c) => c.target === target) ?? null;
|
|
26121
26780
|
}
|
|
26122
26781
|
function writeProducerConfig(config) {
|
|
26123
|
-
const configs =
|
|
26782
|
+
const configs = load6().filter((c) => c.target !== config.target);
|
|
26124
26783
|
configs.push(config);
|
|
26125
|
-
|
|
26784
|
+
save2(configs);
|
|
26126
26785
|
}
|
|
26127
26786
|
function clearProducerConfig(target) {
|
|
26128
|
-
|
|
26787
|
+
save2(load6().filter((c) => c.target !== target));
|
|
26129
26788
|
}
|
|
26130
26789
|
function listProducerConfigs() {
|
|
26131
|
-
return
|
|
26790
|
+
return load6();
|
|
26132
26791
|
}
|
|
26133
26792
|
|
|
26134
26793
|
// src/producers/core-adapter.ts
|
|
@@ -26469,6 +27128,9 @@ var hostCore = {
|
|
|
26469
27128
|
* read of a bundled constant. */
|
|
26470
27129
|
builtinProfileIds: () => [...BUILTIN_PROFILES]
|
|
26471
27130
|
};
|
|
27131
|
+
function resolveContainedProjectPath(projectRoot2, projectPath) {
|
|
27132
|
+
return assertContainedProjectPath(projectRoot2, projectPath);
|
|
27133
|
+
}
|
|
26472
27134
|
function validateProjectAsComplete() {
|
|
26473
27135
|
const config = loadProjectConfig();
|
|
26474
27136
|
return validateAsComplete({ rules: config.rules, projectType: config.projectType });
|
|
@@ -26500,6 +27162,180 @@ var hostSdk = {
|
|
|
26500
27162
|
extractArchive: (archive, destDir, limits) => sdkPortal.extractPack(archive, destDir, limits)
|
|
26501
27163
|
};
|
|
26502
27164
|
|
|
27165
|
+
// src/server/projects.ts
|
|
27166
|
+
var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
27167
|
+
function isValidProjectId(id) {
|
|
27168
|
+
return typeof id === "string" && ID_RE.test(id);
|
|
27169
|
+
}
|
|
27170
|
+
function registryPath(dataDir) {
|
|
27171
|
+
return path44.join(dataDir, "projects.json");
|
|
27172
|
+
}
|
|
27173
|
+
function load7(dataDir) {
|
|
27174
|
+
try {
|
|
27175
|
+
return JSON.parse(fs35.readFileSync(registryPath(dataDir), "utf8"));
|
|
27176
|
+
} catch {
|
|
27177
|
+
return [];
|
|
27178
|
+
}
|
|
27179
|
+
}
|
|
27180
|
+
function save3(dataDir, records) {
|
|
27181
|
+
const p = registryPath(dataDir);
|
|
27182
|
+
fs35.mkdirSync(path44.dirname(p), { recursive: true });
|
|
27183
|
+
const tmp = `${p}.tmp`;
|
|
27184
|
+
fs35.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
27185
|
+
fs35.renameSync(tmp, p);
|
|
27186
|
+
}
|
|
27187
|
+
function projectRoot(dataDir, id) {
|
|
27188
|
+
return path44.join(dataDir, "projects", id);
|
|
27189
|
+
}
|
|
27190
|
+
function existingProjectRoot(dataDir, id) {
|
|
27191
|
+
if (!isValidProjectId(id)) return null;
|
|
27192
|
+
const rec = load7(dataDir).find((r) => r.id === id);
|
|
27193
|
+
return rec ? rec.rootPath : null;
|
|
27194
|
+
}
|
|
27195
|
+
function createProjectRecord(dataDir, id) {
|
|
27196
|
+
if (!isValidProjectId(id)) {
|
|
27197
|
+
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
27198
|
+
}
|
|
27199
|
+
const records = load7(dataDir);
|
|
27200
|
+
if (records.some((r) => r.id === id)) {
|
|
27201
|
+
throw new Error(`Project "${id}" already exists.`);
|
|
27202
|
+
}
|
|
27203
|
+
const root = projectRoot(dataDir, id);
|
|
27204
|
+
fs35.mkdirSync(root, { recursive: true });
|
|
27205
|
+
const record2 = {
|
|
27206
|
+
id,
|
|
27207
|
+
rootPath: root,
|
|
27208
|
+
status: "active",
|
|
27209
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
27210
|
+
};
|
|
27211
|
+
records.push(record2);
|
|
27212
|
+
save3(dataDir, records);
|
|
27213
|
+
return record2;
|
|
27214
|
+
}
|
|
27215
|
+
function registerLocalDevProject(dataDir, id, rootPath) {
|
|
27216
|
+
if (!isValidProjectId(id)) {
|
|
27217
|
+
throw new Error(`Invalid project id "${id}" (allowed: lowercase letters, digits, hyphen).`);
|
|
27218
|
+
}
|
|
27219
|
+
const records = load7(dataDir);
|
|
27220
|
+
const existing = records.find((r) => r.id === id);
|
|
27221
|
+
const record2 = {
|
|
27222
|
+
id,
|
|
27223
|
+
rootPath,
|
|
27224
|
+
status: "active",
|
|
27225
|
+
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
27226
|
+
};
|
|
27227
|
+
const next = existing ? records.map((r) => r.id === id ? record2 : r) : [...records, record2];
|
|
27228
|
+
save3(dataDir, next);
|
|
27229
|
+
return record2;
|
|
27230
|
+
}
|
|
27231
|
+
function listProjectRecords(dataDir) {
|
|
27232
|
+
return load7(dataDir);
|
|
27233
|
+
}
|
|
27234
|
+
function removeProjectRecord(dataDir, id) {
|
|
27235
|
+
const records = load7(dataDir);
|
|
27236
|
+
const rec = records.find((r) => r.id === id);
|
|
27237
|
+
if (rec) {
|
|
27238
|
+
try {
|
|
27239
|
+
fs35.rmSync(rec.rootPath, { recursive: true, force: true });
|
|
27240
|
+
} catch {
|
|
27241
|
+
}
|
|
27242
|
+
}
|
|
27243
|
+
save3(dataDir, records.filter((r) => r.id !== id));
|
|
27244
|
+
}
|
|
27245
|
+
var SUBPROJECT_SEPARATOR = "::";
|
|
27246
|
+
function parseQualifiedSelector(value) {
|
|
27247
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
27248
|
+
const [projectId, ...mounts] = value.split(SUBPROJECT_SEPARATOR);
|
|
27249
|
+
if (!isValidProjectId(projectId)) return null;
|
|
27250
|
+
if (mounts.some((m) => m.trim() === "")) return null;
|
|
27251
|
+
return { projectId, mounts };
|
|
27252
|
+
}
|
|
27253
|
+
function findSubsystemSpec(root, subsystemId) {
|
|
27254
|
+
const specsDir = aiPathsAt(root).specsDir();
|
|
27255
|
+
if (!fs35.existsSync(specsDir)) return null;
|
|
27256
|
+
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
27257
|
+
let raw;
|
|
27258
|
+
try {
|
|
27259
|
+
raw = readYamlFile(file);
|
|
27260
|
+
} catch {
|
|
27261
|
+
continue;
|
|
27262
|
+
}
|
|
27263
|
+
if (!raw || typeof raw !== "object" || !("parentSystem" in raw)) continue;
|
|
27264
|
+
if (raw.id !== subsystemId) continue;
|
|
27265
|
+
const pp = raw.projectPath;
|
|
27266
|
+
return typeof pp === "string" && pp.trim() !== "" ? { projectPath: pp } : {};
|
|
27267
|
+
}
|
|
27268
|
+
return null;
|
|
27269
|
+
}
|
|
27270
|
+
function resolveSubprojectMounts(projectId, projectRoot2, mounts) {
|
|
27271
|
+
let root = projectRoot2;
|
|
27272
|
+
let at = projectId;
|
|
27273
|
+
for (const mount of mounts) {
|
|
27274
|
+
const sub = findSubsystemSpec(root, mount);
|
|
27275
|
+
if (!sub) {
|
|
27276
|
+
throw new Error(
|
|
27277
|
+
`unknown subproject mount "${mount}" on "${at}" \u2014 no subsystem with that id exists in its spec tree`
|
|
27278
|
+
);
|
|
27279
|
+
}
|
|
27280
|
+
if (!sub.projectPath) {
|
|
27281
|
+
throw new Error(
|
|
27282
|
+
`subsystem "${mount}" on "${at}" is not a chained subproject (it carries no projectPath) \u2014 only a subsystem mounted via projectPath can be bound as a subproject`
|
|
27283
|
+
);
|
|
27284
|
+
}
|
|
27285
|
+
root = resolveContainedProjectPath(root, sub.projectPath);
|
|
27286
|
+
at = `${at}${SUBPROJECT_SEPARATOR}${mount}`;
|
|
27287
|
+
}
|
|
27288
|
+
return root;
|
|
27289
|
+
}
|
|
27290
|
+
function assertMintableNarrowingEntry(dataDir, entry) {
|
|
27291
|
+
if (entry === "*") return;
|
|
27292
|
+
const parsed = parseQualifiedSelector(entry);
|
|
27293
|
+
if (!parsed) {
|
|
27294
|
+
throw new Error(
|
|
27295
|
+
`invalid project narrowing entry "${entry}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
|
|
27296
|
+
);
|
|
27297
|
+
}
|
|
27298
|
+
const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
|
|
27299
|
+
if (!rec) throw new Error(`unknown project "${parsed.projectId}"`);
|
|
27300
|
+
if (parsed.mounts.length > 0) {
|
|
27301
|
+
resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
|
|
27302
|
+
}
|
|
27303
|
+
}
|
|
27304
|
+
function narrowingCovers(entry, target) {
|
|
27305
|
+
return target === entry || target.startsWith(entry + SUBPROJECT_SEPARATOR);
|
|
27306
|
+
}
|
|
27307
|
+
function resolveProjectBinding(dataDir, principal, selector) {
|
|
27308
|
+
const authorized = principal.projects;
|
|
27309
|
+
const wildcard = authorized.includes("*");
|
|
27310
|
+
let target;
|
|
27311
|
+
if (selector) {
|
|
27312
|
+
if (!wildcard && !authorized.some((e) => e !== "*" && narrowingCovers(e, selector))) return null;
|
|
27313
|
+
target = selector;
|
|
27314
|
+
} else if (!wildcard && authorized.length === 1) {
|
|
27315
|
+
target = authorized[0];
|
|
27316
|
+
} else {
|
|
27317
|
+
return null;
|
|
27318
|
+
}
|
|
27319
|
+
const parsed = parseQualifiedSelector(target);
|
|
27320
|
+
if (!parsed) return null;
|
|
27321
|
+
const rec = load7(dataDir).find((r) => r.id === parsed.projectId);
|
|
27322
|
+
if (!rec || rec.status !== "active") return null;
|
|
27323
|
+
let rootPath = rec.rootPath;
|
|
27324
|
+
if (parsed.mounts.length > 0) {
|
|
27325
|
+
try {
|
|
27326
|
+
rootPath = resolveSubprojectMounts(parsed.projectId, rec.rootPath, parsed.mounts);
|
|
27327
|
+
} catch {
|
|
27328
|
+
return null;
|
|
27329
|
+
}
|
|
27330
|
+
}
|
|
27331
|
+
const binding = { rootPath, projectId: parsed.projectId };
|
|
27332
|
+
if (parsed.mounts.length > 0) binding.subproject = parsed.mounts.join(SUBPROJECT_SEPARATOR);
|
|
27333
|
+
return binding;
|
|
27334
|
+
}
|
|
27335
|
+
function resolveProjectRoot(dataDir, principal, selector) {
|
|
27336
|
+
return resolveProjectBinding(dataDir, principal, selector)?.rootPath ?? null;
|
|
27337
|
+
}
|
|
27338
|
+
|
|
26503
27339
|
// src/server/errors.ts
|
|
26504
27340
|
var UnauthenticatedError = class extends Error {
|
|
26505
27341
|
constructor() {
|
|
@@ -28525,11 +29361,8 @@ function mintToken(cfg, credential, request) {
|
|
|
28525
29361
|
}
|
|
28526
29362
|
assertNotReservedSubjectId(cfg, [request.ownerUserId]);
|
|
28527
29363
|
const projects = request.projects?.length ? request.projects : ["*"];
|
|
28528
|
-
const knownProjects = new Set(listProjectRecords(cfg.dataDir).map((p) => p.id));
|
|
28529
29364
|
for (const p of projects) {
|
|
28530
|
-
|
|
28531
|
-
throw new Error(`unknown project "${p}"`);
|
|
28532
|
-
}
|
|
29365
|
+
assertMintableNarrowingEntry(cfg.dataDir, p);
|
|
28533
29366
|
}
|
|
28534
29367
|
const owner = findUserByRecordOrSubjectId(cfg.dataDir, request.ownerUserId);
|
|
28535
29368
|
if (owner && owner.status !== "active") {
|
|
@@ -28566,12 +29399,21 @@ function revokeToken(cfg, credential, tokenId) {
|
|
|
28566
29399
|
}
|
|
28567
29400
|
function mintSelfToken(cfg, credential, projectId, write) {
|
|
28568
29401
|
const principal = requirePrincipal5(cfg, credential);
|
|
28569
|
-
|
|
29402
|
+
const parsed = parseQualifiedSelector(projectId);
|
|
29403
|
+
if (!parsed) {
|
|
29404
|
+
throw new Error(
|
|
29405
|
+
`invalid project id "${projectId}" (expected a project id, optionally subproject-qualified as projectId::subsystemId)`
|
|
29406
|
+
);
|
|
29407
|
+
}
|
|
29408
|
+
if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
|
|
28570
29409
|
throw new ForbiddenError("caller lacks project:read on the requested project");
|
|
28571
29410
|
}
|
|
28572
|
-
if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", projectId).value !== "yes") {
|
|
29411
|
+
if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", parsed.projectId).value !== "yes") {
|
|
28573
29412
|
throw new ForbiddenError("caller lacks project:write on the requested project");
|
|
28574
29413
|
}
|
|
29414
|
+
if (parsed.mounts.length > 0) {
|
|
29415
|
+
assertMintableNarrowingEntry(cfg.dataDir, projectId);
|
|
29416
|
+
}
|
|
28575
29417
|
const token = "wk_" + crypto10.randomBytes(24).toString("hex");
|
|
28576
29418
|
const owner = auditActor(principal);
|
|
28577
29419
|
const record2 = {
|
|
@@ -29183,12 +30025,12 @@ function resolveVisibility(observerProjectId, units, placements) {
|
|
|
29183
30025
|
const best = /* @__PURE__ */ new Map();
|
|
29184
30026
|
for (const placement of placements) {
|
|
29185
30027
|
if (placement.projectId === observerProjectId) continue;
|
|
29186
|
-
const
|
|
29187
|
-
if (!
|
|
29188
|
-
const closedOk =
|
|
30028
|
+
const path61 = chainOf(placement.unitId, unitById);
|
|
30029
|
+
if (!path61.length) continue;
|
|
30030
|
+
const closedOk = path61.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
|
|
29189
30031
|
if (!closedOk) continue;
|
|
29190
|
-
const crossTenant = !tenantRoots.has(
|
|
29191
|
-
if (crossTenant && !
|
|
30032
|
+
const crossTenant = !tenantRoots.has(path61[path61.length - 1].id);
|
|
30033
|
+
if (crossTenant && !path61.some(grantedTo)) continue;
|
|
29192
30034
|
if (crossTenant && directUnits.length === 0) continue;
|
|
29193
30035
|
const distance = crossTenant ? "partner" : sameBranch(placement.unitId) ? "department" : "instance";
|
|
29194
30036
|
const existing = best.get(placement.projectId);
|
|
@@ -30127,10 +30969,9 @@ function migratePermissionModel(dataDir, apply) {
|
|
|
30127
30969
|
// src/server/http.ts
|
|
30128
30970
|
var http2 = __toESM(require("http"));
|
|
30129
30971
|
var fs49 = __toESM(require("fs"));
|
|
30130
|
-
var
|
|
30972
|
+
var path58 = __toESM(require("path"));
|
|
30131
30973
|
|
|
30132
30974
|
// src/server/request.ts
|
|
30133
|
-
var path58 = __toESM(require("path"));
|
|
30134
30975
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
30135
30976
|
init_fs();
|
|
30136
30977
|
|
|
@@ -32537,7 +33378,31 @@ function reshapeLandscapeGraph(model, level) {
|
|
|
32537
33378
|
}
|
|
32538
33379
|
const kept = nodes.filter((n) => n.level <= level);
|
|
32539
33380
|
const keptIds = new Set(kept.map((n) => n.id));
|
|
32540
|
-
const
|
|
33381
|
+
const projectNodeIdByProject = /* @__PURE__ */ new Map();
|
|
33382
|
+
const interfaceOwnerProject = /* @__PURE__ */ new Map();
|
|
33383
|
+
for (const n of model.nodes) {
|
|
33384
|
+
if (n.projectId === void 0) continue;
|
|
33385
|
+
if (n.nodeKind === "project") projectNodeIdByProject.set(n.projectId, n.id);
|
|
33386
|
+
else if (n.nodeKind === "publicInterface") interfaceOwnerProject.set(n.id, n.projectId);
|
|
33387
|
+
}
|
|
33388
|
+
const retarget = (endpoint) => {
|
|
33389
|
+
if (keptIds.has(endpoint)) return endpoint;
|
|
33390
|
+
const owner = interfaceOwnerProject.get(endpoint);
|
|
33391
|
+
const projectNode = owner === void 0 ? void 0 : projectNodeIdByProject.get(owner);
|
|
33392
|
+
return projectNode !== void 0 && keptIds.has(projectNode) ? projectNode : void 0;
|
|
33393
|
+
};
|
|
33394
|
+
const edges = [];
|
|
33395
|
+
for (const e of model.edges) {
|
|
33396
|
+
if (keptIds.has(e.from) && keptIds.has(e.to)) {
|
|
33397
|
+
edges.push(e);
|
|
33398
|
+
continue;
|
|
33399
|
+
}
|
|
33400
|
+
if (e.relationId === void 0) continue;
|
|
33401
|
+
const from = retarget(e.from);
|
|
33402
|
+
const to = retarget(e.to);
|
|
33403
|
+
if (from === void 0 || to === void 0 || from === to) continue;
|
|
33404
|
+
edges.push({ ...e, from, to });
|
|
33405
|
+
}
|
|
32541
33406
|
return {
|
|
32542
33407
|
tier: "landscape",
|
|
32543
33408
|
nodes: kept,
|
|
@@ -35144,8 +36009,8 @@ var RealtimeHub = class {
|
|
|
35144
36009
|
* complete the handshake, and register the connection. A bad path or session
|
|
35145
36010
|
* destroys the socket. */
|
|
35146
36011
|
handleUpgrade(cfg, req, socket) {
|
|
35147
|
-
const
|
|
35148
|
-
if (
|
|
36012
|
+
const path61 = (req.url ?? "/").split("?")[0];
|
|
36013
|
+
if (path61 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
|
|
35149
36014
|
socket.destroy();
|
|
35150
36015
|
return;
|
|
35151
36016
|
}
|
|
@@ -35290,7 +36155,7 @@ function deriveMcpOutcome(response) {
|
|
|
35290
36155
|
if (r.result && typeof r.result === "object" && r.result.isError) return "failed";
|
|
35291
36156
|
return "success";
|
|
35292
36157
|
}
|
|
35293
|
-
function auditToolCall(dataDir, principal, projectId, body, outcome) {
|
|
36158
|
+
function auditToolCall(dataDir, principal, projectId, body, outcome, subproject) {
|
|
35294
36159
|
const target = mcpToolTarget(body);
|
|
35295
36160
|
if (!target) return;
|
|
35296
36161
|
const actor = principal.subject ?? {
|
|
@@ -35308,7 +36173,8 @@ function auditToolCall(dataDir, principal, projectId, body, outcome) {
|
|
|
35308
36173
|
actor,
|
|
35309
36174
|
tokenId: principal.tokenId,
|
|
35310
36175
|
projectId,
|
|
35311
|
-
target
|
|
36176
|
+
target,
|
|
36177
|
+
...subproject ? { metadata: JSON.stringify({ subproject }) } : {}
|
|
35312
36178
|
};
|
|
35313
36179
|
try {
|
|
35314
36180
|
appendAuditEvent(dataDir, event, DEFAULT_AUDIT_POLICY);
|
|
@@ -35512,8 +36378,8 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
35512
36378
|
permissionSubject: { subjectId: "anonymous", roleBindings: [], instanceAdmin: true }
|
|
35513
36379
|
};
|
|
35514
36380
|
}
|
|
35515
|
-
const
|
|
35516
|
-
if (!
|
|
36381
|
+
const binding = resolveProjectBinding(cfg.dataDir, principal, projectSelector(req));
|
|
36382
|
+
if (!binding) {
|
|
35517
36383
|
sendJson(res, 403, { error: "project not authorized, unknown, or not specified" });
|
|
35518
36384
|
return;
|
|
35519
36385
|
}
|
|
@@ -35528,19 +36394,20 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
35528
36394
|
});
|
|
35529
36395
|
return;
|
|
35530
36396
|
}
|
|
35531
|
-
await runWithProjectRoot(
|
|
35532
|
-
const projectId =
|
|
36397
|
+
await runWithProjectRoot(binding.rootPath, async () => {
|
|
36398
|
+
const projectId = binding.projectId;
|
|
36399
|
+
const subproject = binding.subproject;
|
|
35533
36400
|
const dispatchedResponse = await dispatchProjectLifecycleTool(cfg, cred, projectId, body);
|
|
35534
36401
|
if (dispatchedResponse !== void 0) {
|
|
35535
36402
|
sendJson(res, 200, dispatchedResponse);
|
|
35536
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse));
|
|
36403
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(dispatchedResponse), subproject);
|
|
35537
36404
|
for (const ch of mcpChangeChannels(body, projectId, dispatchedResponse)) publishChange(ch);
|
|
35538
36405
|
return;
|
|
35539
36406
|
}
|
|
35540
36407
|
const permissionError = dataPlanePermissionError(cfg, principal, projectId, body);
|
|
35541
36408
|
if (permissionError !== void 0) {
|
|
35542
36409
|
sendJson(res, 200, permissionError);
|
|
35543
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError));
|
|
36410
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(permissionError), subproject);
|
|
35544
36411
|
return;
|
|
35545
36412
|
}
|
|
35546
36413
|
const server = createScopedServer();
|
|
@@ -35561,7 +36428,7 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
35561
36428
|
};
|
|
35562
36429
|
await server.connect(transport);
|
|
35563
36430
|
await transport.handleRequest(req, res, body);
|
|
35564
|
-
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response));
|
|
36431
|
+
auditToolCall(cfg.dataDir, principal, projectId, body, deriveMcpOutcome(response), subproject);
|
|
35565
36432
|
for (const ch of mcpChangeChannels(body, projectId, response)) publishChange(ch);
|
|
35566
36433
|
});
|
|
35567
36434
|
}
|
|
@@ -35996,7 +36863,7 @@ function routeData(cfg, req, res) {
|
|
|
35996
36863
|
}
|
|
35997
36864
|
function readExposurePolicyFile(dataDir) {
|
|
35998
36865
|
try {
|
|
35999
|
-
const raw = fs49.readFileSync(
|
|
36866
|
+
const raw = fs49.readFileSync(path58.join(dataDir, "exposure-policy.json"), "utf8");
|
|
36000
36867
|
const parsed = JSON.parse(raw);
|
|
36001
36868
|
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
36002
36869
|
} catch {
|
|
@@ -36681,9 +37548,9 @@ function seedDemoTree() {
|
|
|
36681
37548
|
|
|
36682
37549
|
// src/commands/host.ts
|
|
36683
37550
|
function resolveHostConfig(options) {
|
|
36684
|
-
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] ||
|
|
37551
|
+
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path59.join(os10.homedir(), ".wairon", "data");
|
|
36685
37552
|
if (!process.env["WAIRON_PACKS_DIR"]) {
|
|
36686
|
-
process.env["WAIRON_PACKS_DIR"] =
|
|
37553
|
+
process.env["WAIRON_PACKS_DIR"] = path59.join(dataDir, "packs");
|
|
36687
37554
|
}
|
|
36688
37555
|
const cfg = {
|
|
36689
37556
|
host: options.host || "0.0.0.0",
|
|
@@ -36808,13 +37675,13 @@ function openBrowser(url) {
|
|
|
36808
37675
|
}
|
|
36809
37676
|
async function runDev(options = {}) {
|
|
36810
37677
|
const cwd = process.cwd();
|
|
36811
|
-
if (!fs50.existsSync(
|
|
37678
|
+
if (!fs50.existsSync(path59.join(cwd, ".wai"))) {
|
|
36812
37679
|
throw new WaironError(
|
|
36813
37680
|
"No .wai/ found in the current directory. Run `wairon dev` from a wairon project root (or run `wairon init` first)."
|
|
36814
37681
|
);
|
|
36815
37682
|
}
|
|
36816
37683
|
const hash = crypto20.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
36817
|
-
const dataDir =
|
|
37684
|
+
const dataDir = path59.join(os10.tmpdir(), "wairon-dev", hash);
|
|
36818
37685
|
fs50.mkdirSync(dataDir, { recursive: true });
|
|
36819
37686
|
registerLocalDevProject(dataDir, "local", cwd);
|
|
36820
37687
|
const port = options.port ? Number(options.port) : 8080;
|
|
@@ -37223,8 +38090,8 @@ async function runHostPacks(action, options = {}) {
|
|
|
37223
38090
|
}
|
|
37224
38091
|
case "install": {
|
|
37225
38092
|
if (!options.file) throw new WaironError("`--file <path>` (a declarative pack YAML) is required for install.");
|
|
37226
|
-
const name = options.name ??
|
|
37227
|
-
const content = fs50.readFileSync(
|
|
38093
|
+
const name = options.name ?? path59.basename(options.file).replace(/\.(ya?ml)$/i, "");
|
|
38094
|
+
const content = fs50.readFileSync(path59.resolve(options.file), "utf8");
|
|
37228
38095
|
const desc = project2 ? installProjectPack(cfg, cred, project2, name, content) : installGlobalPack(cfg, cred, name, content);
|
|
37229
38096
|
logger.success(`Installed ${scope} pack "${desc.name}" (${desc.profiles} profile(s), ${desc.languages} language(s)).`);
|
|
37230
38097
|
if (project2) logger.info("Committed with the project \u2014 every clone and CI will enforce it.");
|
|
@@ -37430,13 +38297,27 @@ async function runSurface(action, options = {}) {
|
|
|
37430
38297
|
for (const p of written) logger.info(` ${p}`);
|
|
37431
38298
|
return;
|
|
37432
38299
|
}
|
|
38300
|
+
case "externals": {
|
|
38301
|
+
const entries = listExternalInterfaces();
|
|
38302
|
+
if (!entries.length) {
|
|
38303
|
+
logger.info("No external surfaces available (.wai/surfaces/ holds no snapshots).");
|
|
38304
|
+
return;
|
|
38305
|
+
}
|
|
38306
|
+
const freshness = (f) => f === "fresh" ? import_chalk19.default.green(f) : f === "stale" ? import_chalk19.default.yellow(f) : import_chalk19.default.gray(f);
|
|
38307
|
+
for (const e of entries) {
|
|
38308
|
+
logger.info(
|
|
38309
|
+
`${e.sourceKind.padEnd(8)} ${import_chalk19.default.cyan(e.projectName)} [${e.origin}] ${freshness(e.freshness)} \u2014 ${e.interfaceIds.length ? e.interfaceIds.join(", ") : "(no interfaces)"}`
|
|
38310
|
+
);
|
|
38311
|
+
}
|
|
38312
|
+
return;
|
|
38313
|
+
}
|
|
37433
38314
|
default:
|
|
37434
|
-
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children).`);
|
|
38315
|
+
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, generate-children, externals).`);
|
|
37435
38316
|
}
|
|
37436
38317
|
}
|
|
37437
38318
|
|
|
37438
38319
|
// src/commands/subsystem.ts
|
|
37439
|
-
var
|
|
38320
|
+
var path60 = __toESM(require("path"));
|
|
37440
38321
|
init_logger();
|
|
37441
38322
|
init_errors();
|
|
37442
38323
|
init_fs();
|
|
@@ -37472,9 +38353,9 @@ async function runSubsystemAdd(id, options = {}) {
|
|
|
37472
38353
|
updatedAt: now
|
|
37473
38354
|
};
|
|
37474
38355
|
createChainedSubsystem(subsystem, displayName);
|
|
37475
|
-
const childDir =
|
|
38356
|
+
const childDir = path60.resolve(getProjectRoot(), options.projectPath);
|
|
37476
38357
|
logger.success(`Added external subsystem "${id}" \u2192 ${options.projectPath}`);
|
|
37477
|
-
logger.info(`Scaffolded child project at ${
|
|
38358
|
+
logger.info(`Scaffolded child project at ${path60.relative(process.cwd(), childDir) || "."}`);
|
|
37478
38359
|
logger.info(`Design its spec tree from this parent using namespaced ids (e.g. ${id}::<component>).`);
|
|
37479
38360
|
}
|
|
37480
38361
|
async function runSubsystemMove(id, options = {}) {
|
|
@@ -37497,9 +38378,9 @@ async function runSubsystemExternalize(id, options = {}) {
|
|
|
37497
38378
|
throw new WaironError("--project-path (the subproject destination) is required.");
|
|
37498
38379
|
}
|
|
37499
38380
|
externalizeSubsystem(id, options.projectPath);
|
|
37500
|
-
const childDir =
|
|
38381
|
+
const childDir = path60.resolve(getProjectRoot(), options.projectPath);
|
|
37501
38382
|
logger.success(`Externalized subsystem "${id}" \u2192 ${options.projectPath}`);
|
|
37502
|
-
logger.info(`Moved its specs into ${
|
|
38383
|
+
logger.info(`Moved its specs into ${path60.relative(process.cwd(), childDir) || "."} (now a standalone subproject).`);
|
|
37503
38384
|
logger.info("Move the source code there yourself, then run `wairon validate` to confirm the tree.");
|
|
37504
38385
|
}
|
|
37505
38386
|
async function runSubsystemInternalize(id) {
|
|
@@ -37538,8 +38419,64 @@ program.command("generate").description("Generate agent output files from the sp
|
|
|
37538
38419
|
dryRun: opts.dryRun
|
|
37539
38420
|
});
|
|
37540
38421
|
});
|
|
38422
|
+
async function runLock2(options) {
|
|
38423
|
+
assertProjectInitialized();
|
|
38424
|
+
if (!pathExists(AI_PATHS.specsSystem())) {
|
|
38425
|
+
logger.error("No SDD spec tree found (.wai/specs). Nothing to lock.");
|
|
38426
|
+
process.exit(1);
|
|
38427
|
+
}
|
|
38428
|
+
const projectConfig = loadProjectConfig();
|
|
38429
|
+
logger.info("Analyzing and validating specifications in-memory...");
|
|
38430
|
+
const dry = validateAsComplete({
|
|
38431
|
+
rules: projectConfig.rules,
|
|
38432
|
+
projectType: projectConfig.projectType,
|
|
38433
|
+
scopeSubsystem: options.subsystem,
|
|
38434
|
+
recursive: options.recursive ?? true
|
|
38435
|
+
});
|
|
38436
|
+
const errors = dry.issues.filter((i) => i.severity === "error");
|
|
38437
|
+
if (errors.length > 0) {
|
|
38438
|
+
logger.header("Cannot lock \u2014 the spec tree does not validate as complete");
|
|
38439
|
+
let errorCount = 0;
|
|
38440
|
+
const MAX_PRINT = 100;
|
|
38441
|
+
let skippedErrors = 0;
|
|
38442
|
+
for (const i of errors) {
|
|
38443
|
+
if (errorCount < MAX_PRINT) {
|
|
38444
|
+
logger.error(`${i.specId ? `[${i.specId}] ` : ""}[${i.code}] ${i.message}`);
|
|
38445
|
+
errorCount++;
|
|
38446
|
+
} else {
|
|
38447
|
+
skippedErrors++;
|
|
38448
|
+
}
|
|
38449
|
+
}
|
|
38450
|
+
if (skippedErrors > 0) {
|
|
38451
|
+
logger.error(`... and ${skippedErrors} more error(s) omitted.`);
|
|
38452
|
+
}
|
|
38453
|
+
logger.blank();
|
|
38454
|
+
logger.info("Fix the errors above, then run `wairon lock` again. Nothing was changed.");
|
|
38455
|
+
process.exit(1);
|
|
38456
|
+
}
|
|
38457
|
+
logger.header("Lock SDD specs");
|
|
38458
|
+
const record2 = await runLock(options, dry);
|
|
38459
|
+
if (!record2) {
|
|
38460
|
+
logger.info("Cancelled. Nothing was changed.");
|
|
38461
|
+
return;
|
|
38462
|
+
}
|
|
38463
|
+
const childPaths = generateChildSnapshots();
|
|
38464
|
+
if (childPaths.length > 0) {
|
|
38465
|
+
logger.blank();
|
|
38466
|
+
logger.success(`Regenerated the family/sibling surfaces into ${childPaths.length} chained child snapshot(s):`);
|
|
38467
|
+
for (const p of childPaths) logger.info(` ${p}`);
|
|
38468
|
+
}
|
|
38469
|
+
logger.blank();
|
|
38470
|
+
await runGenerate({ domain: options.subsystem });
|
|
38471
|
+
logger.blank();
|
|
38472
|
+
logger.success("Specs locked and agent topology generated.");
|
|
38473
|
+
logger.info(`Lock record written (.wai/lock.json): stateId ${record2.stateId.algorithm}:${record2.stateId.digest} \u2014 status ${record2.status}.`);
|
|
38474
|
+
logger.warn(
|
|
38475
|
+
"Restart any running AI agent sessions (Claude Code / Antigravity / Codex) so the newly generated implementer agents load \u2014 they are not picked up mid-session."
|
|
38476
|
+
);
|
|
38477
|
+
}
|
|
37541
38478
|
program.command("lock").description("Final check before implementation: validate the spec tree as complete, freeze all specs to complete, and (re)generate the agent topology \u2014 only if it validates").option("-y, --yes", "skip the confirmation prompt (for scripts / CI)").option("--subsystem <id>", "only lock specs in the specified subsystem").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
37542
|
-
await
|
|
38479
|
+
await runLock2({ yes: opts.yes, subsystem: opts.subsystem, recursive: opts.recursive });
|
|
37543
38480
|
});
|
|
37544
38481
|
program.command("validate").description("Validate the project configuration and the SDD Spec Tree").option("--ci", "treat warnings as errors for CI pipelines").option("--subsystem <id>", "only validate the specified subsystem (granular)").option("--no-recursive", "do not recursively validate subprojects").action(async (opts) => {
|
|
37545
38482
|
await runValidate({ ci: opts.ci, subsystem: opts.subsystem, recursive: opts.recursive });
|
|
@@ -37655,7 +38592,7 @@ mcpCmd.command("status").description("Show whether the wairon MCP server is regi
|
|
|
37655
38592
|
program.command("produce <target>").description("project the local project's specs to a producer target (notion | miro)").option("--page <id>", "parent page/board id in the target").option("--token <token>", "integration token (else env, else interactive prompt)").action(async (target, opts) => {
|
|
37656
38593
|
await runProduce(target, { page: opts.page, token: opts.token });
|
|
37657
38594
|
});
|
|
37658
|
-
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
38595
|
+
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children | externals").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
37659
38596
|
await runSurface(action, {
|
|
37660
38597
|
audience: opts.audience,
|
|
37661
38598
|
format: opts.format,
|