@wairon/cli 5.1.1-dev.90 → 5.1.1-dev.92
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 +396 -38
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +268 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3725,6 +3725,7 @@ __export(src_exports, {
|
|
|
3725
3725
|
registerExporter: () => registerExporter,
|
|
3726
3726
|
registerPackRef: () => registerPackRef,
|
|
3727
3727
|
reinjectLocalGuides: () => reinjectLocalGuides,
|
|
3728
|
+
relationHealth: () => relationHealth,
|
|
3728
3729
|
removeDomain: () => removeDomain,
|
|
3729
3730
|
removeExternal: () => removeExternal,
|
|
3730
3731
|
removeMember: () => removeMember,
|
|
@@ -7727,6 +7728,12 @@ var ParentExcerptSchema = import_zod8.z.object({
|
|
|
7727
7728
|
/** sha256:… over the canonical excerpt without its commit. */
|
|
7728
7729
|
digest: import_zod8.z.string()
|
|
7729
7730
|
});
|
|
7731
|
+
function relationHealth(status) {
|
|
7732
|
+
if (status.uses.some((u) => u.state === "changed" || u.state === "removed")) return "incompatible";
|
|
7733
|
+
if (!status.reachable || status.outOfReach || status.uses.some((u) => u.state === "unavailable" || u.state === "unlocked")) return "unavailable";
|
|
7734
|
+
if (status.drifted) return "drifted";
|
|
7735
|
+
return "ok";
|
|
7736
|
+
}
|
|
7730
7737
|
var NamedOpenApiSpecSchema = import_zod8.z.object({
|
|
7731
7738
|
portalId: import_zod8.z.string(),
|
|
7732
7739
|
name: import_zod8.z.string(),
|
|
@@ -9048,12 +9055,16 @@ function memberProjectNodes() {
|
|
|
9048
9055
|
trustedLinks: [],
|
|
9049
9056
|
project: true
|
|
9050
9057
|
}));
|
|
9051
|
-
const
|
|
9052
|
-
|
|
9058
|
+
const rootExternals = family.nodes.find((n) => n.namespace === "")?.externals ?? [];
|
|
9059
|
+
const referenced = rootExternals.filter((e) => e.role === "member").map((e) => ({ id: e.alias, name: e.alias, description: "", trustedLinks: [], project: true, storage: storageBadge(e.sourceKind === "family" ? "contained" : e.sourceKind, e.directory, e.commit) }));
|
|
9060
|
+
const taken = new Set([...contained, ...referenced].map((n) => n.id));
|
|
9061
|
+
const external = rootExternals.filter((e) => e.role !== "member" && e.sourceKind !== "family" && !taken.has(e.alias)).map((e) => ({ id: e.alias, name: e.alias, description: e.project === e.alias ? "" : `project ${e.project}`, trustedLinks: [], project: true, external: true, storage: storageBadge(e.sourceKind, e.directory, e.commit) }));
|
|
9062
|
+
return [...contained, ...referenced, ...external];
|
|
9053
9063
|
}
|
|
9054
9064
|
function storageBadge(storage, directory, commit2) {
|
|
9055
9065
|
if (storage === "git") return `git ${commit2 ? commit2.slice(0, 7) : ""}`.trim();
|
|
9056
9066
|
if (storage === "hosted") return "hosted";
|
|
9067
|
+
if (storage === "unresolved") return "unresolved";
|
|
9057
9068
|
if (storage === "path") return directory ? `../${directory.split(/[\\/]/).pop()}` : "sibling";
|
|
9058
9069
|
return "contained";
|
|
9059
9070
|
}
|
|
@@ -9065,7 +9076,60 @@ function partBadges() {
|
|
|
9065
9076
|
}
|
|
9066
9077
|
return out;
|
|
9067
9078
|
}
|
|
9068
|
-
function
|
|
9079
|
+
function useLabel(use) {
|
|
9080
|
+
if (use.publicName === void 0) return use.member ?? "(the external)";
|
|
9081
|
+
return use.member === void 0 || use.member === "type" ? use.publicName : `${use.publicName}.${use.member}`;
|
|
9082
|
+
}
|
|
9083
|
+
function relationFrom(id, consumer, producer, status, comparedAt) {
|
|
9084
|
+
const changed = status.uses.filter((u) => u.state !== "unchanged");
|
|
9085
|
+
const health = relationHealth(status);
|
|
9086
|
+
return {
|
|
9087
|
+
id,
|
|
9088
|
+
consumer,
|
|
9089
|
+
producer,
|
|
9090
|
+
health,
|
|
9091
|
+
uses: changed.map((u) => ({ name: useLabel(u), state: u.state, ...u.detail !== void 0 ? { detail: u.detail } : {} })),
|
|
9092
|
+
unchanged: status.uses.length - changed.length,
|
|
9093
|
+
...status.pinnedCommit !== void 0 ? { pinnedCommit: status.pinnedCommit } : {},
|
|
9094
|
+
...status.pinnedDigest !== void 0 ? { pinnedDigest: status.pinnedDigest } : {},
|
|
9095
|
+
comparedAt,
|
|
9096
|
+
...health === "unavailable" && status.detail !== void 0 ? { detail: status.detail } : {}
|
|
9097
|
+
};
|
|
9098
|
+
}
|
|
9099
|
+
function statusFor(entry, consumer, producer) {
|
|
9100
|
+
const { alias, node } = producer;
|
|
9101
|
+
return entry.externals.find((s) => alias !== void 0 && s.alias === alias || node !== void 0 && (node.parent === consumer && s.alias === node.mountAlias || s.sourceKind === "family" && node.id !== void 0 && s.project === node.id));
|
|
9102
|
+
}
|
|
9103
|
+
function nameRelations(edges, relations, outsideIds, ownerOf4) {
|
|
9104
|
+
const family = graph();
|
|
9105
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9106
|
+
const out = /* @__PURE__ */ new Map();
|
|
9107
|
+
for (const edge of edges) {
|
|
9108
|
+
if (!edge.consumption) continue;
|
|
9109
|
+
const consumer = ownerOf4(edge.from);
|
|
9110
|
+
const node = outsideIds.has(edge.to) ? void 0 : familyNode(family, ownerOf4(edge.to)) ?? void 0;
|
|
9111
|
+
const alias = outsideIds.has(edge.to) ? edge.to : void 0;
|
|
9112
|
+
const entry = relations.find((r) => r.project === consumer);
|
|
9113
|
+
const status = entry && !entry.detail ? statusFor(entry, consumer, { alias, node }) : void 0;
|
|
9114
|
+
const producer = status?.alias ?? alias ?? (node?.parent === consumer ? node.mountAlias : void 0) ?? node?.id ?? ownerOf4(edge.to);
|
|
9115
|
+
const id = `${consumer}\u2192${producer}`;
|
|
9116
|
+
if (!out.has(id)) {
|
|
9117
|
+
out.set(id, status ? relationFrom(id, consumer, producer, status, entry.comparedAt) : {
|
|
9118
|
+
id,
|
|
9119
|
+
consumer,
|
|
9120
|
+
producer,
|
|
9121
|
+
health: "unavailable",
|
|
9122
|
+
uses: [],
|
|
9123
|
+
unchanged: 0,
|
|
9124
|
+
comparedAt: entry?.comparedAt ?? now,
|
|
9125
|
+
detail: entry?.detail ?? (entry ? `the consumer's statuses name no producer "${producer}"` : "the consumer's statuses were not read")
|
|
9126
|
+
});
|
|
9127
|
+
}
|
|
9128
|
+
edge.relation = id;
|
|
9129
|
+
}
|
|
9130
|
+
return [...out.values()];
|
|
9131
|
+
}
|
|
9132
|
+
function buildCanvasModel(issues = [], relations) {
|
|
9069
9133
|
const system = loadSystemSpec();
|
|
9070
9134
|
const subsystems = loadSubsystemSpecs();
|
|
9071
9135
|
const components = loadComponentSpecs();
|
|
@@ -9173,22 +9237,26 @@ function buildCanvasModel(issues = []) {
|
|
|
9173
9237
|
const badges = partBadges();
|
|
9174
9238
|
const componentSub = new Map(components.map((c) => [c.id, c.subsystem]));
|
|
9175
9239
|
const edges = [];
|
|
9176
|
-
const
|
|
9240
|
+
const outsideIds = new Set(projectNodes.filter((p) => p.storage !== void 0).map((p) => p.id));
|
|
9241
|
+
const owners = graph().owners;
|
|
9242
|
+
const ownerOfSpec = (id) => owners.get(id) ?? "";
|
|
9177
9243
|
for (const comp of components) {
|
|
9178
9244
|
for (const depId of comp.dependsOn) {
|
|
9179
9245
|
const alias = depId.includes("::") ? depId.split("::")[0] : "";
|
|
9180
|
-
if (
|
|
9181
|
-
edges.push({ from: comp.id, to: alias, cross: true });
|
|
9246
|
+
if (outsideIds.has(alias) && !componentIds.has(depId)) {
|
|
9247
|
+
edges.push({ from: comp.id, to: alias, cross: true, consumption: true });
|
|
9182
9248
|
continue;
|
|
9183
9249
|
}
|
|
9184
9250
|
if (!componentIds.has(depId)) continue;
|
|
9185
9251
|
edges.push({
|
|
9186
9252
|
from: comp.id,
|
|
9187
9253
|
to: depId,
|
|
9188
|
-
cross: componentSub.get(depId) !== comp.subsystem
|
|
9254
|
+
cross: componentSub.get(depId) !== comp.subsystem,
|
|
9255
|
+
...ownerOfSpec(depId) !== ownerOfSpec(comp.id) ? { consumption: true } : {}
|
|
9189
9256
|
});
|
|
9190
9257
|
}
|
|
9191
9258
|
}
|
|
9259
|
+
const canvasRelations = relations === void 0 ? void 0 : nameRelations(edges, relations, outsideIds, ownerOfSpec);
|
|
9192
9260
|
const typeSpecs = loadTypeSpecs();
|
|
9193
9261
|
const usedByFor = (t) => {
|
|
9194
9262
|
const qualified = t.subsystem && !t.id.startsWith(`${t.subsystem}::`) ? `${t.subsystem}::${t.id}` : t.id;
|
|
@@ -9310,6 +9378,7 @@ function buildCanvasModel(issues = []) {
|
|
|
9310
9378
|
],
|
|
9311
9379
|
components: modelComponents,
|
|
9312
9380
|
edges,
|
|
9381
|
+
...canvasRelations !== void 0 ? { relations: canvasRelations } : {},
|
|
9313
9382
|
types: modelTypes,
|
|
9314
9383
|
typeEdges,
|
|
9315
9384
|
dataEdges,
|
|
@@ -9483,6 +9552,7 @@ body[data-theme="light"] .selectControl { color-scheme:light; }
|
|
|
9483
9552
|
#cy { position:absolute; inset:0; }
|
|
9484
9553
|
.legend { position:absolute; left:12px; bottom:12px; background:var(--chrome); border:1px solid var(--chrome-border); border-radius:10px; padding:8px 12px; font-size:11px; color:var(--dim); z-index:5; pointer-events:none; }
|
|
9485
9554
|
.legend .sw { display:inline-block; width:10px; height:10px; border-radius:3px; margin-right:4px; vertical-align:-1px; border:1.5px solid; }
|
|
9555
|
+
.reltip { position:absolute; display:none; background:var(--chrome); border:1px solid var(--chrome-border); border-radius:8px; padding:6px 10px; font-size:11px; color:var(--dim); white-space:pre; z-index:6; pointer-events:none; max-width:520px; overflow:hidden; }
|
|
9486
9556
|
/* Stage overlays clear the floating header (52px + 10px top + 10px gap). The
|
|
9487
9557
|
header hides in presentation mode, where they return to the top edge. */
|
|
9488
9558
|
.viewhint { position:absolute; top:72px; left:12px; color:var(--dim); font-size:11px; background:var(--chrome); border:1px solid var(--chrome-border); border-radius:9px; padding:5px 10px; z-index:5; pointer-events:none; }
|
|
@@ -9668,6 +9738,7 @@ body.presentation #exitPresent, body.presentation #presentDetails { display:bloc
|
|
|
9668
9738
|
<div class="viewhint" id="viewHint"></div>
|
|
9669
9739
|
<div id="typesWarn"></div>
|
|
9670
9740
|
<div class="legend" id="legend"></div>
|
|
9741
|
+
<div class="reltip" id="relTip"></div>
|
|
9671
9742
|
</div>
|
|
9672
9743
|
<div id="panelResizer" title="Drag to resize details sidebar"></div>
|
|
9673
9744
|
<div id="panel"></div>
|
|
@@ -9995,6 +10066,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
9995
10066
|
retired: { fill: '#ebe8e4', stroke: '#857b73', text: '#35302b' },
|
|
9996
10067
|
},
|
|
9997
10068
|
issue: '#b3261e', selGlow: '#3465b4', bgLabel: '#f2f5f8', png: '#f2f5f8',
|
|
10069
|
+
relOk: '#2e7d4f', relDrift: '#8a6116', relBad: '#b3261e', relUnknown: '#5f6b78',
|
|
9998
10070
|
},
|
|
9999
10071
|
syw: {
|
|
10000
10072
|
pageEdge: '#7c8ca3', edgeText: '#aebdd2', cross: '#ff6b81', ink: '#eef2f8',
|
|
@@ -10016,6 +10088,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
10016
10088
|
retired: { fill: '#24211f', stroke: '#a8a29e', text: '#e7e5e4' },
|
|
10017
10089
|
},
|
|
10018
10090
|
issue: '#ff6b81', selGlow: '#22ddff', bgLabel: '#0a0a0f', png: '#0a0a0f',
|
|
10091
|
+
relOk: '#34d399', relDrift: '#f59e0b', relBad: '#ff6b81', relUnknown: '#93a1b8',
|
|
10019
10092
|
},
|
|
10020
10093
|
};
|
|
10021
10094
|
|
|
@@ -10078,6 +10151,15 @@ var MODEL = __MODEL_JSON__;
|
|
|
10078
10151
|
{ selector: 'edge.bundle', style: { width: 4.5, opacity: 0.7 } },
|
|
10079
10152
|
{ selector: 'edge.toghost', style: { 'line-style': 'dashed', opacity: 0.75 } },
|
|
10080
10153
|
{ selector: 'edge.inneredge', style: { width: 1.1, 'arrow-scale': 0.6, opacity: 0.8 } },
|
|
10154
|
+
// Relation health on consumption edges: the health colour replaces the
|
|
10155
|
+
// boundary-hop colour, and unavailable is dotted as well as grey, so the
|
|
10156
|
+
// state never rests on colour alone (the glyph is the edge's label).
|
|
10157
|
+
{ selector: 'edge.rel-unchecked', style: { 'line-color': t.pageEdge, 'target-arrow-color': t.pageEdge } },
|
|
10158
|
+
{ selector: 'edge.rel-ok', style: { 'line-color': t.relOk, 'target-arrow-color': t.relOk } },
|
|
10159
|
+
{ selector: 'edge.rel-drifted', style: { 'line-color': t.relDrift, 'target-arrow-color': t.relDrift } },
|
|
10160
|
+
{ selector: 'edge.rel-incompatible', style: { 'line-color': t.relBad, 'target-arrow-color': t.relBad } },
|
|
10161
|
+
{ selector: 'edge.rel-unavailable', style: { 'line-color': t.relUnknown, 'target-arrow-color': t.relUnknown, 'line-style': 'dotted' } },
|
|
10162
|
+
{ selector: '.extProject', style: { 'border-style': 'dashed' } },
|
|
10081
10163
|
{ selector: 'edge.stubHover', style: { 'line-color': t.selGlow, 'target-arrow-color': t.selGlow, width: 2.6, opacity: 1, 'z-compound-depth': 'top' } },
|
|
10082
10164
|
{ selector: '.dimmed', style: { opacity: 0.13 } },
|
|
10083
10165
|
{ selector: '.hasIssue', style: { 'border-color': t.issue, 'border-style': 'dashed', 'border-width': 3 } },
|
|
@@ -10114,6 +10196,83 @@ var MODEL = __MODEL_JSON__;
|
|
|
10114
10196
|
];
|
|
10115
10197
|
}
|
|
10116
10198
|
|
|
10199
|
+
// ---- relation health (consumption edges) ----------------------------------
|
|
10200
|
+
// The model carries relations only when health was computed; without them a
|
|
10201
|
+
// consumption edge is drawn neutral and the legend says it was not checked \u2014
|
|
10202
|
+
// never green for something nobody compared.
|
|
10203
|
+
var HAS_RELATIONS = Array.isArray(MODEL.relations);
|
|
10204
|
+
var REL_BY_ID = {};
|
|
10205
|
+
(MODEL.relations || []).forEach(function (r) { REL_BY_ID[r.id] = r; });
|
|
10206
|
+
var HEALTH_RANK = { ok: 1, drifted: 2, unavailable: 3, incompatible: 4 };
|
|
10207
|
+
var HEALTH_GLYPH = { ok: '\\u2713', drifted: '\\u0394', incompatible: '\\u2715', unavailable: '?' };
|
|
10208
|
+
var HAS_CONSUMPTION = MODEL.edges.some(function (e) { return !!e.consumption; });
|
|
10209
|
+
function edgeHealth(edge) {
|
|
10210
|
+
var r = edge.relation ? REL_BY_ID[edge.relation] : null;
|
|
10211
|
+
return r ? r.health : (HAS_RELATIONS ? 'unavailable' : null);
|
|
10212
|
+
}
|
|
10213
|
+
// Where edges fold into one, the worst health wins.
|
|
10214
|
+
function worseHealth(a, b) {
|
|
10215
|
+
if (!a) return b;
|
|
10216
|
+
if (!b) return a;
|
|
10217
|
+
return HEALTH_RANK[b] > HEALTH_RANK[a] ? b : a;
|
|
10218
|
+
}
|
|
10219
|
+
function relationsComparedAt() {
|
|
10220
|
+
var at = null;
|
|
10221
|
+
(MODEL.relations || []).forEach(function (r) { if (!at || r.comparedAt < at) at = r.comparedAt; });
|
|
10222
|
+
return at;
|
|
10223
|
+
}
|
|
10224
|
+
function shortPin(r) {
|
|
10225
|
+
if (r.pinnedCommit) return r.pinnedCommit.slice(0, 7);
|
|
10226
|
+
return r.pinnedDigest ? r.pinnedDigest.replace(/^[a-z0-9-]+:/, '').slice(0, 12) : '';
|
|
10227
|
+
}
|
|
10228
|
+
function relationName(r) { return (r.consumer || 'this project') + ' \\u2192 ' + r.producer; }
|
|
10229
|
+
function relationText(r) {
|
|
10230
|
+
var lines = [relationName(r) + ': ' + HEALTH_GLYPH[r.health] + ' ' + r.health];
|
|
10231
|
+
r.uses.forEach(function (u) { lines.push(' ' + u.name + ' \\u2014 ' + u.state + (u.detail ? ': ' + u.detail : '')); });
|
|
10232
|
+
if (r.unchanged) lines.push(' ' + r.unchanged + ' use' + (r.unchanged === 1 ? '' : 's') + ' unchanged');
|
|
10233
|
+
if (r.detail) lines.push(' ' + r.detail);
|
|
10234
|
+
var pin = shortPin(r);
|
|
10235
|
+
lines.push((pin ? 'pinned ' + pin + ' \\u00B7 ' : '') + 'compared ' + r.comparedAt);
|
|
10236
|
+
return lines.join('\\n');
|
|
10237
|
+
}
|
|
10238
|
+
function relationHtml(r) {
|
|
10239
|
+
var pin = shortPin(r);
|
|
10240
|
+
return '<div class="method"><div class="mname">' + esc(relationName(r)) + '<span class="grow"></span><span class="chip">' + HEALTH_GLYPH[r.health] + ' ' + esc(r.health) + '</span></div>'
|
|
10241
|
+
+ r.uses.map(function (u) { return '<div class="mdesc">' + esc(u.name) + ' \\u2014 ' + esc(u.state) + (u.detail ? ': ' + esc(u.detail) : '') + '</div>'; }).join('')
|
|
10242
|
+
+ (r.unchanged ? '<div class="mdesc">' + r.unchanged + ' use' + (r.unchanged === 1 ? '' : 's') + ' unchanged</div>' : '')
|
|
10243
|
+
+ (r.detail ? '<div class="mdesc">' + esc(r.detail) + '</div>' : '')
|
|
10244
|
+
+ '<div class="mdesc">' + (pin ? 'pinned ' + esc(pin) + ' \\u00B7 ' : '') + 'compared ' + esc(r.comparedAt) + '</div></div>';
|
|
10245
|
+
}
|
|
10246
|
+
// The relations whose consumption edges start or land inside a node.
|
|
10247
|
+
function relationsTouching(sid) {
|
|
10248
|
+
var seen = {}, out = [];
|
|
10249
|
+
var inside = function (x) {
|
|
10250
|
+
var c = compById[x];
|
|
10251
|
+
return x === sid || x.indexOf(sid + '::') === 0 || !!(c && (c.subsystem === sid || c.subsystem.indexOf(sid + '::') === 0));
|
|
10252
|
+
};
|
|
10253
|
+
MODEL.edges.forEach(function (e) {
|
|
10254
|
+
if (!e.relation || seen[e.relation] || !(inside(e.to) || inside(e.from))) return;
|
|
10255
|
+
seen[e.relation] = true;
|
|
10256
|
+
if (REL_BY_ID[e.relation]) out.push(REL_BY_ID[e.relation]);
|
|
10257
|
+
});
|
|
10258
|
+
return out;
|
|
10259
|
+
}
|
|
10260
|
+
// The one legend row, only when the model has a consumption edge.
|
|
10261
|
+
function relationLegend(t) {
|
|
10262
|
+
if (!HAS_CONSUMPTION) return '';
|
|
10263
|
+
if (!HAS_RELATIONS) return '<br>relations: <span style="color:' + t.pageEdge + '">health not checked</span>';
|
|
10264
|
+
var at = relationsComparedAt();
|
|
10265
|
+
return '<br>relations: <span style="color:' + t.relOk + '">\\u2713 ok</span> <span style="color:' + t.relDrift + '">\\u0394 drifted</span> '
|
|
10266
|
+
+ '<span style="color:' + t.relBad + '">\\u2715 incompatible</span> <span style="color:' + t.relUnknown + '">? unavailable (dotted)</span>'
|
|
10267
|
+
+ (at ? ' \\u00B7 as of ' + esc(at) : '');
|
|
10268
|
+
}
|
|
10269
|
+
// A project's own page, only through the host's open-project hook (the web
|
|
10270
|
+
// app): never in a standalone file or on a shared page, which have none.
|
|
10271
|
+
function openProjectButton(s) {
|
|
10272
|
+
if (!s.recordId || typeof opts === 'undefined' || !opts || typeof opts.onOpenProject !== 'function') return '';
|
|
10273
|
+
return '<div class="openbtn"><button class="tbtn" data-openproject-id="' + esc(s.recordId) + '">Open project \\u2197</button></div>';
|
|
10274
|
+
}
|
|
10275
|
+
|
|
10117
10276
|
function renderLegend() {
|
|
10118
10277
|
var t = THEMES[state.theme];
|
|
10119
10278
|
var sw = function (c) { return '<span class="sw" style="background:' + c.fill + ';border-color:' + c.stroke + '"></span>'; };
|
|
@@ -10127,7 +10286,8 @@ var MODEL = __MODEL_JSON__;
|
|
|
10127
10286
|
sw(t.proxyIn) + '\\u21E0 in-port ' + sw(t.proxyOut) + '\\u21E2 out-port \u2014 bold border = published \xB7 ' +
|
|
10128
10287
|
'<span style="color:' + t.cross + '">red</span> = boundary hop \xB7 double-click = open<br>' +
|
|
10129
10288
|
'on select: <span style="color:' + t.selGlow + '">\\u2192 depends on</span> <span style="color:' + t.warn + '">\\u2190 used by</span>' +
|
|
10130
|
-
(state.dataCoupling ? ' \xB7 <span style="color:' + t.typeV.stroke + '">- - \\u25B8 uses models</span>' : '')
|
|
10289
|
+
(state.dataCoupling ? ' \xB7 <span style="color:' + t.typeV.stroke + '">- - \\u25B8 uses models</span>' : '') +
|
|
10290
|
+
relationLegend(t);
|
|
10131
10291
|
}
|
|
10132
10292
|
|
|
10133
10293
|
// ---- view layout ---------------------------------------------------------------
|
|
@@ -10594,6 +10754,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
10594
10754
|
if (!agg[key]) agg[key] = { src: src, tgt: tgt, n: 0, cross: false, ghost: ghost };
|
|
10595
10755
|
agg[key].n++;
|
|
10596
10756
|
if (edge.cross) agg[key].cross = true;
|
|
10757
|
+
if (edge.consumption) {
|
|
10758
|
+
agg[key].consumption = true;
|
|
10759
|
+
agg[key].health = worseHealth(agg[key].health, edgeHealth(edge));
|
|
10760
|
+
if (edge.relation) {
|
|
10761
|
+
var rels = agg[key].rels || (agg[key].rels = []);
|
|
10762
|
+
if (rels.indexOf(edge.relation) < 0) rels.push(edge.relation);
|
|
10763
|
+
}
|
|
10764
|
+
}
|
|
10597
10765
|
});
|
|
10598
10766
|
return { agg: agg, ghosts: ghosts };
|
|
10599
10767
|
}
|
|
@@ -11154,7 +11322,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
11154
11322
|
var classes, label;
|
|
11155
11323
|
var isPub = e.kind === 'component' && compById[e.id] && compById[e.id].public;
|
|
11156
11324
|
if (e.kind === 'subsystem') {
|
|
11157
|
-
classes = 'subsysBox';
|
|
11325
|
+
classes = 'subsysBox' + (subById[e.id] && subById[e.id].external ? ' extProject' : '');
|
|
11158
11326
|
label = nameOf(e) + (e.hasKids && !inner ? '\\n\\u25B8 open' : '');
|
|
11159
11327
|
} else {
|
|
11160
11328
|
var c = compById[e.id];
|
|
@@ -11362,9 +11530,13 @@ var MODEL = __MODEL_JSON__;
|
|
|
11362
11530
|
var bundle = e.n > 1;
|
|
11363
11531
|
var dim = state.query && (dimmedAnchors[e.src] || dimmedAnchors[e.tgt]);
|
|
11364
11532
|
var route = routeData(e.src, e.tgt, key);
|
|
11533
|
+
// A consumption edge carries its relation's health: the glyph at its middle,
|
|
11534
|
+
// its colour (and line, dotted when unavailable) by class.
|
|
11535
|
+
var glyph = e.health ? HEALTH_GLYPH[e.health] : '';
|
|
11536
|
+
var relCls = e.consumption ? 'rel rel-' + (e.health || 'unchecked') + ' ' : '';
|
|
11365
11537
|
eles.push({
|
|
11366
|
-
data: { id: 'e' + (i++), source: e.src, target: e.tgt, lbl: bundle ? e.n + ' links' : '', cpDist: route.cpDist, cpWeight: route.cpWeight, taxiTurn: route.taxiTurn },
|
|
11367
|
-
classes: 'routed ' + (e.cross ? 'cross ' : '') + (bundle ? 'bundle ' : '') + (e.ghost ? 'toghost ' : '') + (dim ? 'dimmed' : ''),
|
|
11538
|
+
data: { id: 'e' + (i++), source: e.src, target: e.tgt, lbl: (bundle ? e.n + ' links' : '') + (glyph ? (bundle ? ' ' : '') + glyph : ''), rels: (e.rels || []).join('|'), cpDist: route.cpDist, cpWeight: route.cpWeight, taxiTurn: route.taxiTurn },
|
|
11539
|
+
classes: 'routed ' + (e.cross ? 'cross ' : '') + (bundle ? 'bundle ' : '') + (e.ghost ? 'toghost ' : '') + relCls + (dim ? 'dimmed' : ''),
|
|
11368
11540
|
});
|
|
11369
11541
|
});
|
|
11370
11542
|
|
|
@@ -11864,6 +12036,27 @@ var MODEL = __MODEL_JSON__;
|
|
|
11864
12036
|
cy.on('mouseover', 'node', function (ev) { setHover(ev.target); });
|
|
11865
12037
|
cy.on('mouseout', 'node', function (ev) { if (hoveredNode && hoveredNode.id() === ev.target.id()) setHover(null); });
|
|
11866
12038
|
|
|
12039
|
+
// Relation health: hovering a consumption edge shows its relation(s) \u2014 which
|
|
12040
|
+
// uses changed or were removed, why one could not be compared, the pin and
|
|
12041
|
+
// when it was compared; tapping it shows the same in the details panel.
|
|
12042
|
+
function relationsOfEdge(edge) {
|
|
12043
|
+
return (edge.data('rels') || '').split('|').filter(Boolean).map(function (rid) { return REL_BY_ID[rid]; }).filter(Boolean);
|
|
12044
|
+
}
|
|
12045
|
+
cy.on('mouseover', 'edge.rel', function (ev) {
|
|
12046
|
+
var tip = document.getElementById('relTip');
|
|
12047
|
+
var rels = relationsOfEdge(ev.target);
|
|
12048
|
+
tip.textContent = rels.length ? rels.map(relationText).join('\\n\\n') : 'relation health not checked';
|
|
12049
|
+
var at = ev.renderedPosition || { x: 0, y: 0 };
|
|
12050
|
+
tip.style.left = (at.x + 12) + 'px';
|
|
12051
|
+
tip.style.top = (at.y + 12) + 'px';
|
|
12052
|
+
tip.style.display = 'block';
|
|
12053
|
+
});
|
|
12054
|
+
cy.on('mouseout', 'edge.rel', function () { document.getElementById('relTip').style.display = 'none'; });
|
|
12055
|
+
cy.on('tap', 'edge.rel', function (ev) {
|
|
12056
|
+
var ids = ev.target.data('rels') || '';
|
|
12057
|
+
if (ids) select('relation', ids, false);
|
|
12058
|
+
});
|
|
12059
|
+
|
|
11867
12060
|
cy.on('tap', 'node', function (ev) {
|
|
11868
12061
|
var t = idOf(ev.target);
|
|
11869
12062
|
if (t.group) return;
|
|
@@ -11921,7 +12114,15 @@ var MODEL = __MODEL_JSON__;
|
|
|
11921
12114
|
var hasKids = t.kind === 'subsystem'
|
|
11922
12115
|
? (childSubsOf(t.id).length + childCompsOf(t.id).length) > 0
|
|
11923
12116
|
: memberCompsOf(t.id).length > 0;
|
|
11924
|
-
if (hasKids) navigateTo(t.kind, t.id);
|
|
12117
|
+
if (hasKids) { navigateTo(t.kind, t.id); return; }
|
|
12118
|
+
// A project node with nothing to drill into (a referenced or external one):
|
|
12119
|
+
// its own page through the host's open-project hook when it carries a
|
|
12120
|
+
// record id, else its details panel \u2014 its storage and its relations.
|
|
12121
|
+
var ps = t.kind === 'subsystem' ? subById[t.id] : null;
|
|
12122
|
+
if (ps && ps.project) {
|
|
12123
|
+
if (ps.recordId && typeof opts !== 'undefined' && opts && typeof opts.onOpenProject === 'function') { opts.onOpenProject(ps.recordId); return; }
|
|
12124
|
+
select('subsystem', t.id, false);
|
|
12125
|
+
}
|
|
11925
12126
|
});
|
|
11926
12127
|
cy.on('dragfree', 'node', function () { harvestPositions(); });
|
|
11927
12128
|
|
|
@@ -13158,7 +13359,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
13158
13359
|
cy.nodes().removeClass('sel');
|
|
13159
13360
|
cy.edges().removeClass('fieldhl');
|
|
13160
13361
|
if (id) {
|
|
13161
|
-
var node = nodeForRef(kind, id);
|
|
13362
|
+
var node = kind === 'relation' ? cy.collection() : nodeForRef(kind, id);
|
|
13162
13363
|
if (node.length) {
|
|
13163
13364
|
node.addClass('sel');
|
|
13164
13365
|
applyFocus(node);
|
|
@@ -13328,6 +13529,10 @@ var MODEL = __MODEL_JSON__;
|
|
|
13328
13529
|
via.map(function (v) { return chip(v.label, v.kind, v.id); }).join(''), true);
|
|
13329
13530
|
}
|
|
13330
13531
|
}
|
|
13532
|
+
} else if (focusKind === 'relation') {
|
|
13533
|
+
var rl = String(focusId).split('|').map(function (rid) { return REL_BY_ID[rid]; }).filter(Boolean);
|
|
13534
|
+
head = '<h2>' + esc(rl.length === 1 ? relationName(rl[0]) : rl.length + ' relations') + '</h2>' + staticChip('relation');
|
|
13535
|
+
body += rl.map(relationHtml).join('');
|
|
13331
13536
|
} else if (focusKind === 'type') {
|
|
13332
13537
|
var ty = null;
|
|
13333
13538
|
MODEL.types.forEach(function (t2) { if (t2.id === focusId) ty = t2; });
|
|
@@ -13411,8 +13616,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
13411
13616
|
+ (scopeFocus ? staticChip('current view') : '')
|
|
13412
13617
|
+ (scopeFocus ? '' : openViewButton('subsystem', s.id, subKids > 0))
|
|
13413
13618
|
+ openApiButton(subsystemExposesApi(s.id), '')
|
|
13414
|
-
+ openSpecButton('subsystem', s.id)
|
|
13619
|
+
+ openSpecButton('subsystem', s.id)
|
|
13620
|
+
+ (s.project ? staticChip(s.external ? 'external project' : 'project') : '')
|
|
13621
|
+
+ (s.storage ? staticChip(s.storage) : '')
|
|
13622
|
+
+ openProjectButton(s);
|
|
13415
13623
|
body += '<p class="desc">' + esc(s.description) + '</p>';
|
|
13624
|
+
var nodeRels = relationsTouching(s.id);
|
|
13625
|
+
if (nodeRels.length) body += section('Relations', nodeRels.length, nodeRels.map(relationHtml).join(''), true);
|
|
13626
|
+
else if (s.project && HAS_CONSUMPTION && !HAS_RELATIONS) body += section('Relations', null, '<span class="desc">relation health was not checked</span>', true);
|
|
13416
13627
|
if (s.trustedLinks.length) {
|
|
13417
13628
|
body += section('Trusted links (fast lanes)', s.trustedLinks.length, s.trustedLinks.map(function (t2) {
|
|
13418
13629
|
return '<div class="method">' + chip(t2.subsystem, 'subsystem', t2.subsystem) + '<div class="mdesc">' + esc(t2.reason) + '</div></div>';
|
|
@@ -13479,6 +13690,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
13479
13690
|
});
|
|
13480
13691
|
})(ospecs[si]);
|
|
13481
13692
|
}
|
|
13693
|
+
var oprojs = panel.querySelectorAll('[data-openproject-id]');
|
|
13694
|
+
for (var opi = 0; opi < oprojs.length; opi++) {
|
|
13695
|
+
(function (b) {
|
|
13696
|
+
b.addEventListener('click', function () {
|
|
13697
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.onOpenProject === 'function') opts.onOpenProject(b.getAttribute('data-openproject-id'));
|
|
13698
|
+
});
|
|
13699
|
+
})(oprojs[opi]);
|
|
13700
|
+
}
|
|
13482
13701
|
var flows = panel.querySelectorAll('[data-flow-comp]');
|
|
13483
13702
|
for (var j = 0; j < flows.length; j++) {
|
|
13484
13703
|
(function (b) {
|
|
@@ -14074,7 +14293,14 @@ function externalStatus(binding, lock) {
|
|
|
14074
14293
|
const { external } = binding;
|
|
14075
14294
|
const entry = lock?.externals[external.alias];
|
|
14076
14295
|
const pinned = entry !== void 0 && externalsRepository.readSnapshot(external.alias) !== null;
|
|
14077
|
-
const base = {
|
|
14296
|
+
const base = {
|
|
14297
|
+
alias: external.alias,
|
|
14298
|
+
project: external.project,
|
|
14299
|
+
sourceKind: external.sourceKind,
|
|
14300
|
+
pinned,
|
|
14301
|
+
...entry !== void 0 ? { pinnedDigest: entry.digest } : {},
|
|
14302
|
+
...entry?.commit !== void 0 ? { pinnedCommit: entry.commit } : {}
|
|
14303
|
+
};
|
|
14078
14304
|
const unreadable = external.sourceKind === "unresolved" ? hostedOnly(external) ? external.problem : `the external does not resolve: ${external.problem}` : producerUnreadable(external);
|
|
14079
14305
|
let live = null;
|
|
14080
14306
|
let reason = unreadable;
|
|
@@ -14314,7 +14540,8 @@ function reachCeiling(root, family) {
|
|
|
14314
14540
|
}
|
|
14315
14541
|
function within2(dir, ceiling, fn) {
|
|
14316
14542
|
if (ceiling === null) return runWithProjectRoot(dir, fn);
|
|
14317
|
-
|
|
14543
|
+
const lookup2 = getHostedLookup();
|
|
14544
|
+
return runWithProjectBinding(dir, { topRoot: ceiling.topRoot, parentReach: true, narrowed: ceiling.narrowed, ...lookup2 ? { hostedLookup: lookup2 } : {} }, fn);
|
|
14318
14545
|
}
|
|
14319
14546
|
function depthOf(family, node) {
|
|
14320
14547
|
let depth = 0;
|
|
@@ -14365,7 +14592,8 @@ function referencedOf(nodes) {
|
|
|
14365
14592
|
return out;
|
|
14366
14593
|
}
|
|
14367
14594
|
function atReferenced(ref, fn) {
|
|
14368
|
-
|
|
14595
|
+
const lookup2 = getHostedLookup();
|
|
14596
|
+
return runWithProjectBinding(ref.directory, { topRoot: ref.directory, parentReach: true, narrowed: true, ...lookup2 ? { hostedLookup: lookup2 } : {} }, fn);
|
|
14369
14597
|
}
|
|
14370
14598
|
function narrowed(family, selected) {
|
|
14371
14599
|
const keys = /* @__PURE__ */ new Set(["", ...selected.map((s) => s.node.namespace)]);
|
|
@@ -14853,7 +15081,7 @@ function defaultTargetConfig(type) {
|
|
|
14853
15081
|
enabled: true
|
|
14854
15082
|
};
|
|
14855
15083
|
}
|
|
14856
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
15084
|
+
var WAIRON_VERSION = "5.1.1-dev.92";
|
|
14857
15085
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
14858
15086
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
14859
15087
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -25349,10 +25577,11 @@ function familyApprovals(depth) {
|
|
|
25349
25577
|
}
|
|
25350
25578
|
|
|
25351
25579
|
// src/core/diagram.ts
|
|
25352
|
-
function renderDiagram(format) {
|
|
25580
|
+
function renderDiagram(format, relations) {
|
|
25353
25581
|
switch (format) {
|
|
25582
|
+
// Only the canvas carries relation health; the other formats ignore it.
|
|
25354
25583
|
case "canvas":
|
|
25355
|
-
return renderCanvasHtml(buildCanvasModel(diagramIssues()));
|
|
25584
|
+
return renderCanvasHtml(buildCanvasModel(diagramIssues(), relations));
|
|
25356
25585
|
case "mermaid":
|
|
25357
25586
|
return generateComponentDiagram();
|
|
25358
25587
|
case "drawio":
|
|
@@ -25363,8 +25592,8 @@ function renderDiagram(format) {
|
|
|
25363
25592
|
throw new Error(`Unsupported diagram format "${format}" (canvas | mermaid | drawio | excalidraw).`);
|
|
25364
25593
|
}
|
|
25365
25594
|
}
|
|
25366
|
-
function buildCanvasDataModel() {
|
|
25367
|
-
return buildCanvasModel(diagramIssues());
|
|
25595
|
+
function buildCanvasDataModel(relations) {
|
|
25596
|
+
return buildCanvasModel(diagramIssues(), relations);
|
|
25368
25597
|
}
|
|
25369
25598
|
function diagramIssues() {
|
|
25370
25599
|
try {
|
|
@@ -27880,6 +28109,16 @@ function identityFieldsOf(field) {
|
|
|
27880
28109
|
return ["name", "id"];
|
|
27881
28110
|
}
|
|
27882
28111
|
}
|
|
28112
|
+
function withoutAbsentFields(value) {
|
|
28113
|
+
if (Array.isArray(value)) return value.map(withoutAbsentFields);
|
|
28114
|
+
if (typeof value !== "object" || value === null) return value;
|
|
28115
|
+
const out = {};
|
|
28116
|
+
for (const [key, field] of Object.entries(value)) {
|
|
28117
|
+
if (field === void 0 || field === null) continue;
|
|
28118
|
+
out[key] = withoutAbsentFields(field);
|
|
28119
|
+
}
|
|
28120
|
+
return out;
|
|
28121
|
+
}
|
|
27883
28122
|
function carriesEditMarker(item) {
|
|
27884
28123
|
if (item === null || typeof item !== "object") return false;
|
|
27885
28124
|
const o = item;
|
|
@@ -30425,7 +30664,7 @@ var SpecWorkspace = class {
|
|
|
30425
30664
|
return out;
|
|
30426
30665
|
};
|
|
30427
30666
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
30428
|
-
const { unset: _unset, ...mergeableDelta } = delta;
|
|
30667
|
+
const { unset: _unset, ...mergeableDelta } = withoutAbsentFields(delta);
|
|
30429
30668
|
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
30430
30669
|
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
30431
30670
|
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
@@ -39515,7 +39754,10 @@ var externalStatusesOutput = {
|
|
|
39515
39754
|
stale: import_zod11.z.boolean(),
|
|
39516
39755
|
drifted: import_zod11.z.boolean().optional(),
|
|
39517
39756
|
uses: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())),
|
|
39518
|
-
detail: import_zod11.z.string().optional()
|
|
39757
|
+
detail: import_zod11.z.string().optional(),
|
|
39758
|
+
outOfReach: import_zod11.z.boolean().optional(),
|
|
39759
|
+
pinnedDigest: import_zod11.z.string().optional().describe("The content digest the lock recorded; provenance, never compared here."),
|
|
39760
|
+
pinnedCommit: import_zod11.z.string().optional().describe("The producer commit the lock recorded, for one in another repository; provenance only.")
|
|
39519
39761
|
})).describe("One status per declared external, in declaration order; EXTERNAL_CHECK_UNAVAILABLE marks what could not be compared."),
|
|
39520
39762
|
...staleServerOutput
|
|
39521
39763
|
};
|
|
@@ -41146,7 +41388,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
41146
41388
|
inputSchema: {
|
|
41147
41389
|
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project export table, each entry a re-export {from, component, interface, typeDef, as, name, type, details, audience: project|department|instance|partner|external} \u2014 from alone re-exports everything a subsystem exports, and a legacy {id, subsystem} reads as {as, from})"),
|
|
41148
41390
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
41149
|
-
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. Only STORED jumps relocate: a jump the delta itself writes (on an inserted, appended or edited step) names its target in the numbering the WHOLE delta leaves behind, after all of its inserts and deletes, and no later step of the same delta moves it. A method the delta ADDS without a narrative is an intent-level method with none ([]). A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
41391
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. Only STORED jumps relocate: a jump the delta itself writes (on an inserted, appended or edited step) names its target in the numbering the WHOLE delta leaves behind, after all of its inserts and deletes, and no later step of the same delta moves it. A method the delta ADDS without a narrative is an intent-level method with none ([]). An element the delta NAMES merges field by field: a field it leaves out, or passes as null, keeps its stored value at EVERY depth (a method named without "narrative" keeps its steps, a step named without "catches" keeps them), while "narrative": [] CLEARS that method's steps \u2014 so leave a method you are not editing out of the delta. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
41150
41392
|
dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
|
|
41151
41393
|
},
|
|
41152
41394
|
outputSchema: specChangeReportOutput
|
|
@@ -41707,6 +41949,7 @@ function loadProjectConfig3() {
|
|
|
41707
41949
|
registerExporter,
|
|
41708
41950
|
registerPackRef,
|
|
41709
41951
|
reinjectLocalGuides,
|
|
41952
|
+
relationHealth,
|
|
41710
41953
|
removeDomain,
|
|
41711
41954
|
removeExternal,
|
|
41712
41955
|
removeMember,
|