@wairon/cli 5.1.1-dev.91 → 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 +384 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +256 -23
- 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.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.92";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -3229,6 +3229,12 @@ function typeSourceFiles(type) {
|
|
|
3229
3229
|
for (const method2 of type.methods ?? []) add2(method2.sourcePath);
|
|
3230
3230
|
return files;
|
|
3231
3231
|
}
|
|
3232
|
+
function relationHealth(status2) {
|
|
3233
|
+
if (status2.uses.some((u) => u.state === "changed" || u.state === "removed")) return "incompatible";
|
|
3234
|
+
if (!status2.reachable || status2.outOfReach || status2.uses.some((u) => u.state === "unavailable" || u.state === "unlocked")) return "unavailable";
|
|
3235
|
+
if (status2.drifted) return "drifted";
|
|
3236
|
+
return "ok";
|
|
3237
|
+
}
|
|
3232
3238
|
var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, TechnologySchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, ExternalLockEntrySchema, ExternalsLockSchema, ParentExcerptSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
3233
3239
|
var init_specs = __esm({
|
|
3234
3240
|
"src/models/specs.ts"() {
|
|
@@ -5585,12 +5591,16 @@ function memberProjectNodes() {
|
|
|
5585
5591
|
trustedLinks: [],
|
|
5586
5592
|
project: true
|
|
5587
5593
|
}));
|
|
5588
|
-
const
|
|
5589
|
-
|
|
5594
|
+
const rootExternals = family.nodes.find((n) => n.namespace === "")?.externals ?? [];
|
|
5595
|
+
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) }));
|
|
5596
|
+
const taken = new Set([...contained, ...referenced].map((n) => n.id));
|
|
5597
|
+
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) }));
|
|
5598
|
+
return [...contained, ...referenced, ...external];
|
|
5590
5599
|
}
|
|
5591
5600
|
function storageBadge(storage, directory, commit3) {
|
|
5592
5601
|
if (storage === "git") return `git ${commit3 ? commit3.slice(0, 7) : ""}`.trim();
|
|
5593
5602
|
if (storage === "hosted") return "hosted";
|
|
5603
|
+
if (storage === "unresolved") return "unresolved";
|
|
5594
5604
|
if (storage === "path") return directory ? `../${directory.split(/[\\/]/).pop()}` : "sibling";
|
|
5595
5605
|
return "contained";
|
|
5596
5606
|
}
|
|
@@ -5602,7 +5612,60 @@ function partBadges() {
|
|
|
5602
5612
|
}
|
|
5603
5613
|
return out;
|
|
5604
5614
|
}
|
|
5605
|
-
function
|
|
5615
|
+
function useLabel(use) {
|
|
5616
|
+
if (use.publicName === void 0) return use.member ?? "(the external)";
|
|
5617
|
+
return use.member === void 0 || use.member === "type" ? use.publicName : `${use.publicName}.${use.member}`;
|
|
5618
|
+
}
|
|
5619
|
+
function relationFrom(id, consumer, producer, status2, comparedAt) {
|
|
5620
|
+
const changed = status2.uses.filter((u) => u.state !== "unchanged");
|
|
5621
|
+
const health = relationHealth(status2);
|
|
5622
|
+
return {
|
|
5623
|
+
id,
|
|
5624
|
+
consumer,
|
|
5625
|
+
producer,
|
|
5626
|
+
health,
|
|
5627
|
+
uses: changed.map((u) => ({ name: useLabel(u), state: u.state, ...u.detail !== void 0 ? { detail: u.detail } : {} })),
|
|
5628
|
+
unchanged: status2.uses.length - changed.length,
|
|
5629
|
+
...status2.pinnedCommit !== void 0 ? { pinnedCommit: status2.pinnedCommit } : {},
|
|
5630
|
+
...status2.pinnedDigest !== void 0 ? { pinnedDigest: status2.pinnedDigest } : {},
|
|
5631
|
+
comparedAt,
|
|
5632
|
+
...health === "unavailable" && status2.detail !== void 0 ? { detail: status2.detail } : {}
|
|
5633
|
+
};
|
|
5634
|
+
}
|
|
5635
|
+
function statusFor(entry, consumer, producer) {
|
|
5636
|
+
const { alias, node } = producer;
|
|
5637
|
+
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));
|
|
5638
|
+
}
|
|
5639
|
+
function nameRelations(edges, relations, outsideIds, ownerOf4) {
|
|
5640
|
+
const family = graph();
|
|
5641
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5642
|
+
const out = /* @__PURE__ */ new Map();
|
|
5643
|
+
for (const edge of edges) {
|
|
5644
|
+
if (!edge.consumption) continue;
|
|
5645
|
+
const consumer = ownerOf4(edge.from);
|
|
5646
|
+
const node = outsideIds.has(edge.to) ? void 0 : familyNode(family, ownerOf4(edge.to)) ?? void 0;
|
|
5647
|
+
const alias = outsideIds.has(edge.to) ? edge.to : void 0;
|
|
5648
|
+
const entry = relations.find((r) => r.project === consumer);
|
|
5649
|
+
const status2 = entry && !entry.detail ? statusFor(entry, consumer, { alias, node }) : void 0;
|
|
5650
|
+
const producer = status2?.alias ?? alias ?? (node?.parent === consumer ? node.mountAlias : void 0) ?? node?.id ?? ownerOf4(edge.to);
|
|
5651
|
+
const id = `${consumer}\u2192${producer}`;
|
|
5652
|
+
if (!out.has(id)) {
|
|
5653
|
+
out.set(id, status2 ? relationFrom(id, consumer, producer, status2, entry.comparedAt) : {
|
|
5654
|
+
id,
|
|
5655
|
+
consumer,
|
|
5656
|
+
producer,
|
|
5657
|
+
health: "unavailable",
|
|
5658
|
+
uses: [],
|
|
5659
|
+
unchanged: 0,
|
|
5660
|
+
comparedAt: entry?.comparedAt ?? now,
|
|
5661
|
+
detail: entry?.detail ?? (entry ? `the consumer's statuses name no producer "${producer}"` : "the consumer's statuses were not read")
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
edge.relation = id;
|
|
5665
|
+
}
|
|
5666
|
+
return [...out.values()];
|
|
5667
|
+
}
|
|
5668
|
+
function buildCanvasModel(issues = [], relations) {
|
|
5606
5669
|
const system = loadSystemSpec();
|
|
5607
5670
|
const subsystems = loadSubsystemSpecs();
|
|
5608
5671
|
const components = loadComponentSpecs();
|
|
@@ -5710,22 +5773,26 @@ function buildCanvasModel(issues = []) {
|
|
|
5710
5773
|
const badges = partBadges();
|
|
5711
5774
|
const componentSub = new Map(components.map((c) => [c.id, c.subsystem]));
|
|
5712
5775
|
const edges = [];
|
|
5713
|
-
const
|
|
5776
|
+
const outsideIds = new Set(projectNodes.filter((p) => p.storage !== void 0).map((p) => p.id));
|
|
5777
|
+
const owners = graph().owners;
|
|
5778
|
+
const ownerOfSpec = (id) => owners.get(id) ?? "";
|
|
5714
5779
|
for (const comp of components) {
|
|
5715
5780
|
for (const depId of comp.dependsOn) {
|
|
5716
5781
|
const alias = depId.includes("::") ? depId.split("::")[0] : "";
|
|
5717
|
-
if (
|
|
5718
|
-
edges.push({ from: comp.id, to: alias, cross: true });
|
|
5782
|
+
if (outsideIds.has(alias) && !componentIds.has(depId)) {
|
|
5783
|
+
edges.push({ from: comp.id, to: alias, cross: true, consumption: true });
|
|
5719
5784
|
continue;
|
|
5720
5785
|
}
|
|
5721
5786
|
if (!componentIds.has(depId)) continue;
|
|
5722
5787
|
edges.push({
|
|
5723
5788
|
from: comp.id,
|
|
5724
5789
|
to: depId,
|
|
5725
|
-
cross: componentSub.get(depId) !== comp.subsystem
|
|
5790
|
+
cross: componentSub.get(depId) !== comp.subsystem,
|
|
5791
|
+
...ownerOfSpec(depId) !== ownerOfSpec(comp.id) ? { consumption: true } : {}
|
|
5726
5792
|
});
|
|
5727
5793
|
}
|
|
5728
5794
|
}
|
|
5795
|
+
const canvasRelations2 = relations === void 0 ? void 0 : nameRelations(edges, relations, outsideIds, ownerOfSpec);
|
|
5729
5796
|
const typeSpecs = loadTypeSpecs();
|
|
5730
5797
|
const usedByFor = (t) => {
|
|
5731
5798
|
const qualified = t.subsystem && !t.id.startsWith(`${t.subsystem}::`) ? `${t.subsystem}::${t.id}` : t.id;
|
|
@@ -5847,6 +5914,7 @@ function buildCanvasModel(issues = []) {
|
|
|
5847
5914
|
],
|
|
5848
5915
|
components: modelComponents,
|
|
5849
5916
|
edges,
|
|
5917
|
+
...canvasRelations2 !== void 0 ? { relations: canvasRelations2 } : {},
|
|
5850
5918
|
types: modelTypes,
|
|
5851
5919
|
typeEdges,
|
|
5852
5920
|
dataEdges,
|
|
@@ -6029,6 +6097,7 @@ body[data-theme="light"] .selectControl { color-scheme:light; }
|
|
|
6029
6097
|
#cy { position:absolute; inset:0; }
|
|
6030
6098
|
.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; }
|
|
6031
6099
|
.legend .sw { display:inline-block; width:10px; height:10px; border-radius:3px; margin-right:4px; vertical-align:-1px; border:1.5px solid; }
|
|
6100
|
+
.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; }
|
|
6032
6101
|
/* Stage overlays clear the floating header (52px + 10px top + 10px gap). The
|
|
6033
6102
|
header hides in presentation mode, where they return to the top edge. */
|
|
6034
6103
|
.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; }
|
|
@@ -6214,6 +6283,7 @@ body.presentation #exitPresent, body.presentation #presentDetails { display:bloc
|
|
|
6214
6283
|
<div class="viewhint" id="viewHint"></div>
|
|
6215
6284
|
<div id="typesWarn"></div>
|
|
6216
6285
|
<div class="legend" id="legend"></div>
|
|
6286
|
+
<div class="reltip" id="relTip"></div>
|
|
6217
6287
|
</div>
|
|
6218
6288
|
<div id="panelResizer" title="Drag to resize details sidebar"></div>
|
|
6219
6289
|
<div id="panel"></div>
|
|
@@ -6541,6 +6611,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
6541
6611
|
retired: { fill: '#ebe8e4', stroke: '#857b73', text: '#35302b' },
|
|
6542
6612
|
},
|
|
6543
6613
|
issue: '#b3261e', selGlow: '#3465b4', bgLabel: '#f2f5f8', png: '#f2f5f8',
|
|
6614
|
+
relOk: '#2e7d4f', relDrift: '#8a6116', relBad: '#b3261e', relUnknown: '#5f6b78',
|
|
6544
6615
|
},
|
|
6545
6616
|
syw: {
|
|
6546
6617
|
pageEdge: '#7c8ca3', edgeText: '#aebdd2', cross: '#ff6b81', ink: '#eef2f8',
|
|
@@ -6562,6 +6633,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
6562
6633
|
retired: { fill: '#24211f', stroke: '#a8a29e', text: '#e7e5e4' },
|
|
6563
6634
|
},
|
|
6564
6635
|
issue: '#ff6b81', selGlow: '#22ddff', bgLabel: '#0a0a0f', png: '#0a0a0f',
|
|
6636
|
+
relOk: '#34d399', relDrift: '#f59e0b', relBad: '#ff6b81', relUnknown: '#93a1b8',
|
|
6565
6637
|
},
|
|
6566
6638
|
};
|
|
6567
6639
|
|
|
@@ -6624,6 +6696,15 @@ var MODEL = __MODEL_JSON__;
|
|
|
6624
6696
|
{ selector: 'edge.bundle', style: { width: 4.5, opacity: 0.7 } },
|
|
6625
6697
|
{ selector: 'edge.toghost', style: { 'line-style': 'dashed', opacity: 0.75 } },
|
|
6626
6698
|
{ selector: 'edge.inneredge', style: { width: 1.1, 'arrow-scale': 0.6, opacity: 0.8 } },
|
|
6699
|
+
// Relation health on consumption edges: the health colour replaces the
|
|
6700
|
+
// boundary-hop colour, and unavailable is dotted as well as grey, so the
|
|
6701
|
+
// state never rests on colour alone (the glyph is the edge's label).
|
|
6702
|
+
{ selector: 'edge.rel-unchecked', style: { 'line-color': t.pageEdge, 'target-arrow-color': t.pageEdge } },
|
|
6703
|
+
{ selector: 'edge.rel-ok', style: { 'line-color': t.relOk, 'target-arrow-color': t.relOk } },
|
|
6704
|
+
{ selector: 'edge.rel-drifted', style: { 'line-color': t.relDrift, 'target-arrow-color': t.relDrift } },
|
|
6705
|
+
{ selector: 'edge.rel-incompatible', style: { 'line-color': t.relBad, 'target-arrow-color': t.relBad } },
|
|
6706
|
+
{ selector: 'edge.rel-unavailable', style: { 'line-color': t.relUnknown, 'target-arrow-color': t.relUnknown, 'line-style': 'dotted' } },
|
|
6707
|
+
{ selector: '.extProject', style: { 'border-style': 'dashed' } },
|
|
6627
6708
|
{ selector: 'edge.stubHover', style: { 'line-color': t.selGlow, 'target-arrow-color': t.selGlow, width: 2.6, opacity: 1, 'z-compound-depth': 'top' } },
|
|
6628
6709
|
{ selector: '.dimmed', style: { opacity: 0.13 } },
|
|
6629
6710
|
{ selector: '.hasIssue', style: { 'border-color': t.issue, 'border-style': 'dashed', 'border-width': 3 } },
|
|
@@ -6660,6 +6741,83 @@ var MODEL = __MODEL_JSON__;
|
|
|
6660
6741
|
];
|
|
6661
6742
|
}
|
|
6662
6743
|
|
|
6744
|
+
// ---- relation health (consumption edges) ----------------------------------
|
|
6745
|
+
// The model carries relations only when health was computed; without them a
|
|
6746
|
+
// consumption edge is drawn neutral and the legend says it was not checked \u2014
|
|
6747
|
+
// never green for something nobody compared.
|
|
6748
|
+
var HAS_RELATIONS = Array.isArray(MODEL.relations);
|
|
6749
|
+
var REL_BY_ID = {};
|
|
6750
|
+
(MODEL.relations || []).forEach(function (r) { REL_BY_ID[r.id] = r; });
|
|
6751
|
+
var HEALTH_RANK = { ok: 1, drifted: 2, unavailable: 3, incompatible: 4 };
|
|
6752
|
+
var HEALTH_GLYPH = { ok: '\\u2713', drifted: '\\u0394', incompatible: '\\u2715', unavailable: '?' };
|
|
6753
|
+
var HAS_CONSUMPTION = MODEL.edges.some(function (e) { return !!e.consumption; });
|
|
6754
|
+
function edgeHealth(edge) {
|
|
6755
|
+
var r = edge.relation ? REL_BY_ID[edge.relation] : null;
|
|
6756
|
+
return r ? r.health : (HAS_RELATIONS ? 'unavailable' : null);
|
|
6757
|
+
}
|
|
6758
|
+
// Where edges fold into one, the worst health wins.
|
|
6759
|
+
function worseHealth(a, b) {
|
|
6760
|
+
if (!a) return b;
|
|
6761
|
+
if (!b) return a;
|
|
6762
|
+
return HEALTH_RANK[b] > HEALTH_RANK[a] ? b : a;
|
|
6763
|
+
}
|
|
6764
|
+
function relationsComparedAt() {
|
|
6765
|
+
var at = null;
|
|
6766
|
+
(MODEL.relations || []).forEach(function (r) { if (!at || r.comparedAt < at) at = r.comparedAt; });
|
|
6767
|
+
return at;
|
|
6768
|
+
}
|
|
6769
|
+
function shortPin(r) {
|
|
6770
|
+
if (r.pinnedCommit) return r.pinnedCommit.slice(0, 7);
|
|
6771
|
+
return r.pinnedDigest ? r.pinnedDigest.replace(/^[a-z0-9-]+:/, '').slice(0, 12) : '';
|
|
6772
|
+
}
|
|
6773
|
+
function relationName(r) { return (r.consumer || 'this project') + ' \\u2192 ' + r.producer; }
|
|
6774
|
+
function relationText(r) {
|
|
6775
|
+
var lines = [relationName(r) + ': ' + HEALTH_GLYPH[r.health] + ' ' + r.health];
|
|
6776
|
+
r.uses.forEach(function (u) { lines.push(' ' + u.name + ' \\u2014 ' + u.state + (u.detail ? ': ' + u.detail : '')); });
|
|
6777
|
+
if (r.unchanged) lines.push(' ' + r.unchanged + ' use' + (r.unchanged === 1 ? '' : 's') + ' unchanged');
|
|
6778
|
+
if (r.detail) lines.push(' ' + r.detail);
|
|
6779
|
+
var pin = shortPin(r);
|
|
6780
|
+
lines.push((pin ? 'pinned ' + pin + ' \\u00B7 ' : '') + 'compared ' + r.comparedAt);
|
|
6781
|
+
return lines.join('\\n');
|
|
6782
|
+
}
|
|
6783
|
+
function relationHtml(r) {
|
|
6784
|
+
var pin = shortPin(r);
|
|
6785
|
+
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>'
|
|
6786
|
+
+ r.uses.map(function (u) { return '<div class="mdesc">' + esc(u.name) + ' \\u2014 ' + esc(u.state) + (u.detail ? ': ' + esc(u.detail) : '') + '</div>'; }).join('')
|
|
6787
|
+
+ (r.unchanged ? '<div class="mdesc">' + r.unchanged + ' use' + (r.unchanged === 1 ? '' : 's') + ' unchanged</div>' : '')
|
|
6788
|
+
+ (r.detail ? '<div class="mdesc">' + esc(r.detail) + '</div>' : '')
|
|
6789
|
+
+ '<div class="mdesc">' + (pin ? 'pinned ' + esc(pin) + ' \\u00B7 ' : '') + 'compared ' + esc(r.comparedAt) + '</div></div>';
|
|
6790
|
+
}
|
|
6791
|
+
// The relations whose consumption edges start or land inside a node.
|
|
6792
|
+
function relationsTouching(sid) {
|
|
6793
|
+
var seen = {}, out = [];
|
|
6794
|
+
var inside = function (x) {
|
|
6795
|
+
var c = compById[x];
|
|
6796
|
+
return x === sid || x.indexOf(sid + '::') === 0 || !!(c && (c.subsystem === sid || c.subsystem.indexOf(sid + '::') === 0));
|
|
6797
|
+
};
|
|
6798
|
+
MODEL.edges.forEach(function (e) {
|
|
6799
|
+
if (!e.relation || seen[e.relation] || !(inside(e.to) || inside(e.from))) return;
|
|
6800
|
+
seen[e.relation] = true;
|
|
6801
|
+
if (REL_BY_ID[e.relation]) out.push(REL_BY_ID[e.relation]);
|
|
6802
|
+
});
|
|
6803
|
+
return out;
|
|
6804
|
+
}
|
|
6805
|
+
// The one legend row, only when the model has a consumption edge.
|
|
6806
|
+
function relationLegend(t) {
|
|
6807
|
+
if (!HAS_CONSUMPTION) return '';
|
|
6808
|
+
if (!HAS_RELATIONS) return '<br>relations: <span style="color:' + t.pageEdge + '">health not checked</span>';
|
|
6809
|
+
var at = relationsComparedAt();
|
|
6810
|
+
return '<br>relations: <span style="color:' + t.relOk + '">\\u2713 ok</span> <span style="color:' + t.relDrift + '">\\u0394 drifted</span> '
|
|
6811
|
+
+ '<span style="color:' + t.relBad + '">\\u2715 incompatible</span> <span style="color:' + t.relUnknown + '">? unavailable (dotted)</span>'
|
|
6812
|
+
+ (at ? ' \\u00B7 as of ' + esc(at) : '');
|
|
6813
|
+
}
|
|
6814
|
+
// A project's own page, only through the host's open-project hook (the web
|
|
6815
|
+
// app): never in a standalone file or on a shared page, which have none.
|
|
6816
|
+
function openProjectButton(s) {
|
|
6817
|
+
if (!s.recordId || typeof opts === 'undefined' || !opts || typeof opts.onOpenProject !== 'function') return '';
|
|
6818
|
+
return '<div class="openbtn"><button class="tbtn" data-openproject-id="' + esc(s.recordId) + '">Open project \\u2197</button></div>';
|
|
6819
|
+
}
|
|
6820
|
+
|
|
6663
6821
|
function renderLegend() {
|
|
6664
6822
|
var t = THEMES[state.theme];
|
|
6665
6823
|
var sw = function (c) { return '<span class="sw" style="background:' + c.fill + ';border-color:' + c.stroke + '"></span>'; };
|
|
@@ -6673,7 +6831,8 @@ var MODEL = __MODEL_JSON__;
|
|
|
6673
6831
|
sw(t.proxyIn) + '\\u21E0 in-port ' + sw(t.proxyOut) + '\\u21E2 out-port \u2014 bold border = published \xB7 ' +
|
|
6674
6832
|
'<span style="color:' + t.cross + '">red</span> = boundary hop \xB7 double-click = open<br>' +
|
|
6675
6833
|
'on select: <span style="color:' + t.selGlow + '">\\u2192 depends on</span> <span style="color:' + t.warn + '">\\u2190 used by</span>' +
|
|
6676
|
-
(state.dataCoupling ? ' \xB7 <span style="color:' + t.typeV.stroke + '">- - \\u25B8 uses models</span>' : '')
|
|
6834
|
+
(state.dataCoupling ? ' \xB7 <span style="color:' + t.typeV.stroke + '">- - \\u25B8 uses models</span>' : '') +
|
|
6835
|
+
relationLegend(t);
|
|
6677
6836
|
}
|
|
6678
6837
|
|
|
6679
6838
|
// ---- view layout ---------------------------------------------------------------
|
|
@@ -7140,6 +7299,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
7140
7299
|
if (!agg[key]) agg[key] = { src: src, tgt: tgt, n: 0, cross: false, ghost: ghost };
|
|
7141
7300
|
agg[key].n++;
|
|
7142
7301
|
if (edge.cross) agg[key].cross = true;
|
|
7302
|
+
if (edge.consumption) {
|
|
7303
|
+
agg[key].consumption = true;
|
|
7304
|
+
agg[key].health = worseHealth(agg[key].health, edgeHealth(edge));
|
|
7305
|
+
if (edge.relation) {
|
|
7306
|
+
var rels = agg[key].rels || (agg[key].rels = []);
|
|
7307
|
+
if (rels.indexOf(edge.relation) < 0) rels.push(edge.relation);
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7143
7310
|
});
|
|
7144
7311
|
return { agg: agg, ghosts: ghosts };
|
|
7145
7312
|
}
|
|
@@ -7700,7 +7867,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
7700
7867
|
var classes, label;
|
|
7701
7868
|
var isPub = e.kind === 'component' && compById[e.id] && compById[e.id].public;
|
|
7702
7869
|
if (e.kind === 'subsystem') {
|
|
7703
|
-
classes = 'subsysBox';
|
|
7870
|
+
classes = 'subsysBox' + (subById[e.id] && subById[e.id].external ? ' extProject' : '');
|
|
7704
7871
|
label = nameOf(e) + (e.hasKids && !inner ? '\\n\\u25B8 open' : '');
|
|
7705
7872
|
} else {
|
|
7706
7873
|
var c = compById[e.id];
|
|
@@ -7908,9 +8075,13 @@ var MODEL = __MODEL_JSON__;
|
|
|
7908
8075
|
var bundle = e.n > 1;
|
|
7909
8076
|
var dim = state.query && (dimmedAnchors[e.src] || dimmedAnchors[e.tgt]);
|
|
7910
8077
|
var route = routeData(e.src, e.tgt, key);
|
|
8078
|
+
// A consumption edge carries its relation's health: the glyph at its middle,
|
|
8079
|
+
// its colour (and line, dotted when unavailable) by class.
|
|
8080
|
+
var glyph = e.health ? HEALTH_GLYPH[e.health] : '';
|
|
8081
|
+
var relCls = e.consumption ? 'rel rel-' + (e.health || 'unchecked') + ' ' : '';
|
|
7911
8082
|
eles.push({
|
|
7912
|
-
data: { id: 'e' + (i++), source: e.src, target: e.tgt, lbl: bundle ? e.n + ' links' : '', cpDist: route.cpDist, cpWeight: route.cpWeight, taxiTurn: route.taxiTurn },
|
|
7913
|
-
classes: 'routed ' + (e.cross ? 'cross ' : '') + (bundle ? 'bundle ' : '') + (e.ghost ? 'toghost ' : '') + (dim ? 'dimmed' : ''),
|
|
8083
|
+
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 },
|
|
8084
|
+
classes: 'routed ' + (e.cross ? 'cross ' : '') + (bundle ? 'bundle ' : '') + (e.ghost ? 'toghost ' : '') + relCls + (dim ? 'dimmed' : ''),
|
|
7914
8085
|
});
|
|
7915
8086
|
});
|
|
7916
8087
|
|
|
@@ -8410,6 +8581,27 @@ var MODEL = __MODEL_JSON__;
|
|
|
8410
8581
|
cy.on('mouseover', 'node', function (ev) { setHover(ev.target); });
|
|
8411
8582
|
cy.on('mouseout', 'node', function (ev) { if (hoveredNode && hoveredNode.id() === ev.target.id()) setHover(null); });
|
|
8412
8583
|
|
|
8584
|
+
// Relation health: hovering a consumption edge shows its relation(s) \u2014 which
|
|
8585
|
+
// uses changed or were removed, why one could not be compared, the pin and
|
|
8586
|
+
// when it was compared; tapping it shows the same in the details panel.
|
|
8587
|
+
function relationsOfEdge(edge) {
|
|
8588
|
+
return (edge.data('rels') || '').split('|').filter(Boolean).map(function (rid) { return REL_BY_ID[rid]; }).filter(Boolean);
|
|
8589
|
+
}
|
|
8590
|
+
cy.on('mouseover', 'edge.rel', function (ev) {
|
|
8591
|
+
var tip = document.getElementById('relTip');
|
|
8592
|
+
var rels = relationsOfEdge(ev.target);
|
|
8593
|
+
tip.textContent = rels.length ? rels.map(relationText).join('\\n\\n') : 'relation health not checked';
|
|
8594
|
+
var at = ev.renderedPosition || { x: 0, y: 0 };
|
|
8595
|
+
tip.style.left = (at.x + 12) + 'px';
|
|
8596
|
+
tip.style.top = (at.y + 12) + 'px';
|
|
8597
|
+
tip.style.display = 'block';
|
|
8598
|
+
});
|
|
8599
|
+
cy.on('mouseout', 'edge.rel', function () { document.getElementById('relTip').style.display = 'none'; });
|
|
8600
|
+
cy.on('tap', 'edge.rel', function (ev) {
|
|
8601
|
+
var ids = ev.target.data('rels') || '';
|
|
8602
|
+
if (ids) select('relation', ids, false);
|
|
8603
|
+
});
|
|
8604
|
+
|
|
8413
8605
|
cy.on('tap', 'node', function (ev) {
|
|
8414
8606
|
var t = idOf(ev.target);
|
|
8415
8607
|
if (t.group) return;
|
|
@@ -8467,7 +8659,15 @@ var MODEL = __MODEL_JSON__;
|
|
|
8467
8659
|
var hasKids = t.kind === 'subsystem'
|
|
8468
8660
|
? (childSubsOf(t.id).length + childCompsOf(t.id).length) > 0
|
|
8469
8661
|
: memberCompsOf(t.id).length > 0;
|
|
8470
|
-
if (hasKids) navigateTo(t.kind, t.id);
|
|
8662
|
+
if (hasKids) { navigateTo(t.kind, t.id); return; }
|
|
8663
|
+
// A project node with nothing to drill into (a referenced or external one):
|
|
8664
|
+
// its own page through the host's open-project hook when it carries a
|
|
8665
|
+
// record id, else its details panel \u2014 its storage and its relations.
|
|
8666
|
+
var ps = t.kind === 'subsystem' ? subById[t.id] : null;
|
|
8667
|
+
if (ps && ps.project) {
|
|
8668
|
+
if (ps.recordId && typeof opts !== 'undefined' && opts && typeof opts.onOpenProject === 'function') { opts.onOpenProject(ps.recordId); return; }
|
|
8669
|
+
select('subsystem', t.id, false);
|
|
8670
|
+
}
|
|
8471
8671
|
});
|
|
8472
8672
|
cy.on('dragfree', 'node', function () { harvestPositions(); });
|
|
8473
8673
|
|
|
@@ -9704,7 +9904,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
9704
9904
|
cy.nodes().removeClass('sel');
|
|
9705
9905
|
cy.edges().removeClass('fieldhl');
|
|
9706
9906
|
if (id) {
|
|
9707
|
-
var node = nodeForRef(kind, id);
|
|
9907
|
+
var node = kind === 'relation' ? cy.collection() : nodeForRef(kind, id);
|
|
9708
9908
|
if (node.length) {
|
|
9709
9909
|
node.addClass('sel');
|
|
9710
9910
|
applyFocus(node);
|
|
@@ -9874,6 +10074,10 @@ var MODEL = __MODEL_JSON__;
|
|
|
9874
10074
|
via.map(function (v) { return chip(v.label, v.kind, v.id); }).join(''), true);
|
|
9875
10075
|
}
|
|
9876
10076
|
}
|
|
10077
|
+
} else if (focusKind === 'relation') {
|
|
10078
|
+
var rl = String(focusId).split('|').map(function (rid) { return REL_BY_ID[rid]; }).filter(Boolean);
|
|
10079
|
+
head = '<h2>' + esc(rl.length === 1 ? relationName(rl[0]) : rl.length + ' relations') + '</h2>' + staticChip('relation');
|
|
10080
|
+
body += rl.map(relationHtml).join('');
|
|
9877
10081
|
} else if (focusKind === 'type') {
|
|
9878
10082
|
var ty = null;
|
|
9879
10083
|
MODEL.types.forEach(function (t2) { if (t2.id === focusId) ty = t2; });
|
|
@@ -9957,8 +10161,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
9957
10161
|
+ (scopeFocus ? staticChip('current view') : '')
|
|
9958
10162
|
+ (scopeFocus ? '' : openViewButton('subsystem', s.id, subKids > 0))
|
|
9959
10163
|
+ openApiButton(subsystemExposesApi(s.id), '')
|
|
9960
|
-
+ openSpecButton('subsystem', s.id)
|
|
10164
|
+
+ openSpecButton('subsystem', s.id)
|
|
10165
|
+
+ (s.project ? staticChip(s.external ? 'external project' : 'project') : '')
|
|
10166
|
+
+ (s.storage ? staticChip(s.storage) : '')
|
|
10167
|
+
+ openProjectButton(s);
|
|
9961
10168
|
body += '<p class="desc">' + esc(s.description) + '</p>';
|
|
10169
|
+
var nodeRels = relationsTouching(s.id);
|
|
10170
|
+
if (nodeRels.length) body += section('Relations', nodeRels.length, nodeRels.map(relationHtml).join(''), true);
|
|
10171
|
+
else if (s.project && HAS_CONSUMPTION && !HAS_RELATIONS) body += section('Relations', null, '<span class="desc">relation health was not checked</span>', true);
|
|
9962
10172
|
if (s.trustedLinks.length) {
|
|
9963
10173
|
body += section('Trusted links (fast lanes)', s.trustedLinks.length, s.trustedLinks.map(function (t2) {
|
|
9964
10174
|
return '<div class="method">' + chip(t2.subsystem, 'subsystem', t2.subsystem) + '<div class="mdesc">' + esc(t2.reason) + '</div></div>';
|
|
@@ -10025,6 +10235,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
10025
10235
|
});
|
|
10026
10236
|
})(ospecs[si]);
|
|
10027
10237
|
}
|
|
10238
|
+
var oprojs = panel.querySelectorAll('[data-openproject-id]');
|
|
10239
|
+
for (var opi = 0; opi < oprojs.length; opi++) {
|
|
10240
|
+
(function (b) {
|
|
10241
|
+
b.addEventListener('click', function () {
|
|
10242
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.onOpenProject === 'function') opts.onOpenProject(b.getAttribute('data-openproject-id'));
|
|
10243
|
+
});
|
|
10244
|
+
})(oprojs[opi]);
|
|
10245
|
+
}
|
|
10028
10246
|
var flows = panel.querySelectorAll('[data-flow-comp]');
|
|
10029
10247
|
for (var j = 0; j < flows.length; j++) {
|
|
10030
10248
|
(function (b) {
|
|
@@ -11077,7 +11295,14 @@ function externalStatus(binding, lock) {
|
|
|
11077
11295
|
const { external } = binding;
|
|
11078
11296
|
const entry = lock?.externals[external.alias];
|
|
11079
11297
|
const pinned = entry !== void 0 && externalsRepository.readSnapshot(external.alias) !== null;
|
|
11080
|
-
const base = {
|
|
11298
|
+
const base = {
|
|
11299
|
+
alias: external.alias,
|
|
11300
|
+
project: external.project,
|
|
11301
|
+
sourceKind: external.sourceKind,
|
|
11302
|
+
pinned,
|
|
11303
|
+
...entry !== void 0 ? { pinnedDigest: entry.digest } : {},
|
|
11304
|
+
...entry?.commit !== void 0 ? { pinnedCommit: entry.commit } : {}
|
|
11305
|
+
};
|
|
11081
11306
|
const unreadable = external.sourceKind === "unresolved" ? hostedOnly(external) ? external.problem : `the external does not resolve: ${external.problem}` : producerUnreadable(external);
|
|
11082
11307
|
let live = null;
|
|
11083
11308
|
let reason = unreadable;
|
|
@@ -11361,7 +11586,8 @@ function reachCeiling(root, family) {
|
|
|
11361
11586
|
}
|
|
11362
11587
|
function within2(dir, ceiling, fn) {
|
|
11363
11588
|
if (ceiling === null) return runWithProjectRoot(dir, fn);
|
|
11364
|
-
|
|
11589
|
+
const lookup2 = getHostedLookup();
|
|
11590
|
+
return runWithProjectBinding(dir, { topRoot: ceiling.topRoot, parentReach: true, narrowed: ceiling.narrowed, ...lookup2 ? { hostedLookup: lookup2 } : {} }, fn);
|
|
11365
11591
|
}
|
|
11366
11592
|
function depthOf(family, node) {
|
|
11367
11593
|
let depth = 0;
|
|
@@ -11412,7 +11638,8 @@ function referencedOf(nodes) {
|
|
|
11412
11638
|
return out;
|
|
11413
11639
|
}
|
|
11414
11640
|
function atReferenced(ref, fn) {
|
|
11415
|
-
|
|
11641
|
+
const lookup2 = getHostedLookup();
|
|
11642
|
+
return runWithProjectBinding(ref.directory, { topRoot: ref.directory, parentReach: true, narrowed: true, ...lookup2 ? { hostedLookup: lookup2 } : {} }, fn);
|
|
11416
11643
|
}
|
|
11417
11644
|
function narrowed(family, selected) {
|
|
11418
11645
|
const keys = /* @__PURE__ */ new Set(["", ...selected.map((s) => s.node.namespace)]);
|
|
@@ -11725,6 +11952,86 @@ function projectApprovals(depth) {
|
|
|
11725
11952
|
}
|
|
11726
11953
|
return entries;
|
|
11727
11954
|
}
|
|
11955
|
+
function relationReach(root) {
|
|
11956
|
+
const granted = getRequestParentReach();
|
|
11957
|
+
if (!granted) return null;
|
|
11958
|
+
const lookup2 = getHostedLookup();
|
|
11959
|
+
const base = granted.parentReach ? { topRoot: granted.topRoot ?? root, parentReach: true, ...granted.narrowed ? { narrowed: true } : {} } : { topRoot: root, parentReach: true, narrowed: true };
|
|
11960
|
+
return { ...base, ...lookup2 ? { hostedLookup: lookup2 } : {} };
|
|
11961
|
+
}
|
|
11962
|
+
function outsideReach(node) {
|
|
11963
|
+
const lookup2 = getHostedLookup();
|
|
11964
|
+
if (!lookup2 || node.namespace === "") return null;
|
|
11965
|
+
return lookup2(node.id ?? node.namespace) === null ? `the project "${node.id ?? node.namespace}" lies outside this request's reach` : null;
|
|
11966
|
+
}
|
|
11967
|
+
function referenceUse(ref) {
|
|
11968
|
+
return ref.publicName ?? ref.resolved.split("::").pop() ?? ref.authored;
|
|
11969
|
+
}
|
|
11970
|
+
function failureAt(ref, failures) {
|
|
11971
|
+
const site = `${ref.specId} (${ref.position})`;
|
|
11972
|
+
const last = ref.resolved.split("::").pop() ?? "";
|
|
11973
|
+
return failures.find((i) => i.resolution.callSite === site && (i.resolution.canonicalTarget === ref.authored || (i.resolution.canonicalTarget ?? "").endsWith(`::${last}`)));
|
|
11974
|
+
}
|
|
11975
|
+
function containedStatus(child, refs, failures) {
|
|
11976
|
+
const base = { alias: child.mountAlias, project: child.id ?? child.namespace, sourceKind: "family", pinned: false };
|
|
11977
|
+
const unreachable = outsideReach(child);
|
|
11978
|
+
if (unreachable) {
|
|
11979
|
+
const uses2 = [...new Set(refs.map(referenceUse))].map((name) => ({ publicName: name, state: "unavailable", code: "EXTERNAL_CHECK_UNAVAILABLE", detail: unreachable }));
|
|
11980
|
+
return { ...base, reachable: false, stale: false, outOfReach: true, uses: uses2, detail: unreachable };
|
|
11981
|
+
}
|
|
11982
|
+
const uses = [];
|
|
11983
|
+
for (const ref of refs) {
|
|
11984
|
+
const failed = failureAt(ref, failures);
|
|
11985
|
+
const use = failed ? { publicName: referenceUse(ref), state: "removed", code: failed.code, detail: failed.resolution.reason } : { publicName: referenceUse(ref), state: "unchanged" };
|
|
11986
|
+
if (!uses.some((u) => u.publicName === use.publicName && u.state === use.state)) uses.push(use);
|
|
11987
|
+
}
|
|
11988
|
+
return { ...base, reachable: true, stale: uses.some((u) => u.state === "removed"), uses, detail: "a contained member, read live \u2014 nothing is pinned" };
|
|
11989
|
+
}
|
|
11990
|
+
function containedRelations(answered) {
|
|
11991
|
+
const own = graph();
|
|
11992
|
+
const consumed = own.nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0 && !answered.some((s) => s.alias === n.mountAlias)).map((child) => ({
|
|
11993
|
+
child,
|
|
11994
|
+
refs: own.authoredReferences.filter((r) => (own.owners.get(r.specId) ?? "") === "" && r.producer === child.namespace && r.binding !== "local")
|
|
11995
|
+
})).filter((c) => c.refs.length > 0);
|
|
11996
|
+
if (consumed.length === 0) return [];
|
|
11997
|
+
const config = configOrNull();
|
|
11998
|
+
const failures = validateProject({ rules: config?.rules, projectType: config?.projectType }).issues.filter((i) => i.resolution !== void 0 && i.resolution.outcome !== "resolved");
|
|
11999
|
+
return consumed.map(({ child, refs }) => containedStatus(child, refs, failures));
|
|
12000
|
+
}
|
|
12001
|
+
function relationsAt(key, dir, reach2) {
|
|
12002
|
+
const bound2 = (fn) => reach2 === null ? runWithProjectRoot(dir, fn) : runWithProjectBinding(dir, reach2, fn);
|
|
12003
|
+
return bound2(() => {
|
|
12004
|
+
const statuses = getExternalsStatus2();
|
|
12005
|
+
const contained = containedRelations(statuses);
|
|
12006
|
+
return { project: key, externals: [...statuses, ...contained], comparedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
12007
|
+
});
|
|
12008
|
+
}
|
|
12009
|
+
function unopened(key, detail) {
|
|
12010
|
+
return { project: key, externals: [], comparedAt: (/* @__PURE__ */ new Date()).toISOString(), detail };
|
|
12011
|
+
}
|
|
12012
|
+
function familyRelations() {
|
|
12013
|
+
const family = graph();
|
|
12014
|
+
const root = path17.resolve(getProjectRoot());
|
|
12015
|
+
const reach2 = relationReach(root);
|
|
12016
|
+
const out = [];
|
|
12017
|
+
for (const node of family.nodes) {
|
|
12018
|
+
const unreachable = outsideReach(node);
|
|
12019
|
+
if (unreachable || !fs12.existsSync(node.directory)) {
|
|
12020
|
+
out.push(unopened(node.namespace, unreachable ?? `its root ${node.directory} does not exist`));
|
|
12021
|
+
continue;
|
|
12022
|
+
}
|
|
12023
|
+
out.push(relationsAt(node.namespace, node.directory, reach2));
|
|
12024
|
+
}
|
|
12025
|
+
for (const ref of referencedOf(family.nodes)) {
|
|
12026
|
+
if (ref.directory === void 0) {
|
|
12027
|
+
out.push(unopened(ref.key, ref.problem ?? "its root could not be located"));
|
|
12028
|
+
continue;
|
|
12029
|
+
}
|
|
12030
|
+
const lookup2 = getHostedLookup();
|
|
12031
|
+
out.push(relationsAt(ref.key, ref.directory, { topRoot: ref.directory, parentReach: true, narrowed: true, ...lookup2 ? { hostedLookup: lookup2 } : {} }));
|
|
12032
|
+
}
|
|
12033
|
+
return out;
|
|
12034
|
+
}
|
|
11728
12035
|
function requiredBy(key) {
|
|
11729
12036
|
return key === "" ? "the family root" : `"${key}"`;
|
|
11730
12037
|
}
|
|
@@ -23340,6 +23647,9 @@ function directMemberSubjects() {
|
|
|
23340
23647
|
function familyApprovals(depth) {
|
|
23341
23648
|
return projectApprovals(depth);
|
|
23342
23649
|
}
|
|
23650
|
+
function familyRelations2() {
|
|
23651
|
+
return familyRelations();
|
|
23652
|
+
}
|
|
23343
23653
|
var EXCERPT_EPOCH, GRADE_ORDER, NEVER_SUBJECT;
|
|
23344
23654
|
var init_validation = __esm({
|
|
23345
23655
|
"src/core/validation.ts"() {
|
|
@@ -23367,10 +23677,11 @@ var init_validation = __esm({
|
|
|
23367
23677
|
});
|
|
23368
23678
|
|
|
23369
23679
|
// src/core/diagram.ts
|
|
23370
|
-
function renderDiagram(format) {
|
|
23680
|
+
function renderDiagram(format, relations) {
|
|
23371
23681
|
switch (format) {
|
|
23682
|
+
// Only the canvas carries relation health; the other formats ignore it.
|
|
23372
23683
|
case "canvas":
|
|
23373
|
-
return renderCanvasHtml(buildCanvasModel(diagramIssues()));
|
|
23684
|
+
return renderCanvasHtml(buildCanvasModel(diagramIssues(), relations));
|
|
23374
23685
|
case "mermaid":
|
|
23375
23686
|
return generateComponentDiagram();
|
|
23376
23687
|
case "drawio":
|
|
@@ -23381,8 +23692,8 @@ function renderDiagram(format) {
|
|
|
23381
23692
|
throw new Error(`Unsupported diagram format "${format}" (canvas | mermaid | drawio | excalidraw).`);
|
|
23382
23693
|
}
|
|
23383
23694
|
}
|
|
23384
|
-
function buildCanvasDataModel() {
|
|
23385
|
-
return buildCanvasModel(diagramIssues());
|
|
23695
|
+
function buildCanvasDataModel(relations) {
|
|
23696
|
+
return buildCanvasModel(diagramIssues(), relations);
|
|
23386
23697
|
}
|
|
23387
23698
|
function diagramIssues() {
|
|
23388
23699
|
try {
|
|
@@ -43240,7 +43551,10 @@ var init_server = __esm({
|
|
|
43240
43551
|
stale: import_zod11.z.boolean(),
|
|
43241
43552
|
drifted: import_zod11.z.boolean().optional(),
|
|
43242
43553
|
uses: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())),
|
|
43243
|
-
detail: import_zod11.z.string().optional()
|
|
43554
|
+
detail: import_zod11.z.string().optional(),
|
|
43555
|
+
outOfReach: import_zod11.z.boolean().optional(),
|
|
43556
|
+
pinnedDigest: import_zod11.z.string().optional().describe("The content digest the lock recorded; provenance, never compared here."),
|
|
43557
|
+
pinnedCommit: import_zod11.z.string().optional().describe("The producer commit the lock recorded, for one in another repository; provenance only.")
|
|
43244
43558
|
})).describe("One status per declared external, in declaration order; EXTERNAL_CHECK_UNAVAILABLE marks what could not be compared."),
|
|
43245
43559
|
...staleServerOutput
|
|
43246
43560
|
};
|
|
@@ -47113,12 +47427,16 @@ function parseSequenceRef(ref) {
|
|
|
47113
47427
|
}
|
|
47114
47428
|
return { component: ref.slice(0, sep18), method: ref.slice(sep18 + 1) };
|
|
47115
47429
|
}
|
|
47430
|
+
function canvasRelations(options) {
|
|
47431
|
+
if (options.health === false) return void 0;
|
|
47432
|
+
return familyRelations2();
|
|
47433
|
+
}
|
|
47116
47434
|
async function runDiagram(rawOptions = {}) {
|
|
47117
47435
|
assertProjectInitialized();
|
|
47118
47436
|
const options = applyFormat(rawOptions);
|
|
47119
47437
|
if (options.canvas && !options.all) {
|
|
47120
47438
|
const dest2 = options.out ?? path58.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
47121
|
-
writeArtifact(dest2, renderDiagram("canvas"));
|
|
47439
|
+
writeArtifact(dest2, renderDiagram("canvas", canvasRelations(options)));
|
|
47122
47440
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
47123
47441
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
|
|
47124
47442
|
return;
|
|
@@ -47140,7 +47458,7 @@ async function runDiagram(rawOptions = {}) {
|
|
|
47140
47458
|
const wantsMermaid = options.format?.toLowerCase().startsWith("mermaid") || !!options.subsystem || !!options.sequence;
|
|
47141
47459
|
if (!options.all && !options.sequence && !wantsMermaid) {
|
|
47142
47460
|
const dest2 = options.out ?? path58.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
47143
|
-
writeArtifact(dest2, renderDiagram("canvas"));
|
|
47461
|
+
writeArtifact(dest2, renderDiagram("canvas", canvasRelations(options)));
|
|
47144
47462
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
47145
47463
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
|
|
47146
47464
|
return;
|
|
@@ -47155,7 +47473,7 @@ async function runDiagram(rawOptions = {}) {
|
|
|
47155
47473
|
for (const file of files) {
|
|
47156
47474
|
writeArtifact(path58.join(outDir, file.relPath), toMarkdown(file));
|
|
47157
47475
|
}
|
|
47158
|
-
writeArtifact(path58.join(outDir, "canvas.html"), renderDiagram("canvas"));
|
|
47476
|
+
writeArtifact(path58.join(outDir, "canvas.html"), renderDiagram("canvas", canvasRelations(options)));
|
|
47159
47477
|
writeArtifact(path58.join(outDir, "architecture.drawio"), renderDiagram("drawio"));
|
|
47160
47478
|
writeArtifact(path58.join(outDir, "architecture.excalidraw"), renderDiagram("excalidraw"));
|
|
47161
47479
|
const graph2 = loadSpecGraph();
|
|
@@ -51468,6 +51786,7 @@ init_validation();
|
|
|
51468
51786
|
init_errors();
|
|
51469
51787
|
init_validation();
|
|
51470
51788
|
init_validation();
|
|
51789
|
+
init_validation();
|
|
51471
51790
|
function validateAsComplete2() {
|
|
51472
51791
|
const config = loadProjectConfig();
|
|
51473
51792
|
if (!config) throw new ProjectNotInitializedError();
|
|
@@ -57239,20 +57558,31 @@ function getSnapshot2(dataDir, snapshotId) {
|
|
|
57239
57558
|
function getSnapshotArtifact(dataDir, snapshotId, kind, portalId) {
|
|
57240
57559
|
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).getArtifact(snapshotId, kind, portalId);
|
|
57241
57560
|
}
|
|
57561
|
+
function creatorReach(dataDir, principal, projectId, root) {
|
|
57562
|
+
const familyRoot = listFamilyRecords(dataDir, projectId)[0];
|
|
57563
|
+
const top = familyRoot !== void 0 ? resolveProjectRoot(dataDir, principal, familyRoot.id) : null;
|
|
57564
|
+
return {
|
|
57565
|
+
topRoot: top ?? root,
|
|
57566
|
+
parentReach: top !== null,
|
|
57567
|
+
hostedLookup: (id) => resolveProjectRoot(dataDir, principal, id)
|
|
57568
|
+
};
|
|
57569
|
+
}
|
|
57242
57570
|
function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
57243
57571
|
const root = resolveProjectRoot(dataDir, principal, projectId);
|
|
57244
57572
|
if (!root) throw new ForbiddenError("project not authorized or unknown");
|
|
57573
|
+
const relations = runWithProjectBinding(root, creatorReach(dataDir, principal, projectId, root), () => familyRelations2());
|
|
57245
57574
|
return runWithProjectRoot(root, () => {
|
|
57246
57575
|
const snapshot = {
|
|
57247
57576
|
id: "",
|
|
57248
57577
|
projectId,
|
|
57249
57578
|
view,
|
|
57250
57579
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57251
|
-
//
|
|
57252
|
-
|
|
57580
|
+
// Step 3: the canvas model is the primary payload the shared view renders,
|
|
57581
|
+
// with those relations on its consumption edges and no member record ids.
|
|
57582
|
+
canvasModel: JSON.stringify(buildCanvasDataModel(relations))
|
|
57253
57583
|
};
|
|
57254
57584
|
if (artifacts.includes("html")) {
|
|
57255
|
-
snapshot.html = renderDiagram("canvas");
|
|
57585
|
+
snapshot.html = renderDiagram("canvas", relations);
|
|
57256
57586
|
}
|
|
57257
57587
|
if (artifacts.includes("openapi")) {
|
|
57258
57588
|
const result = exportSurface2("project", "openapi");
|
|
@@ -57819,17 +58149,34 @@ function getWebProjectCanvasModel(cfg, sessionId, projectId) {
|
|
|
57819
58149
|
if (!root) {
|
|
57820
58150
|
throw new ForbiddenError("project not authorized or unknown");
|
|
57821
58151
|
}
|
|
57822
|
-
const
|
|
57823
|
-
const
|
|
58152
|
+
const listed = listProjects2(cfg, sessionId);
|
|
58153
|
+
const relations = runWithProjectBinding(root, readReach(cfg, projectId, root, listed), () => familyRelations2());
|
|
58154
|
+
const model = runWithProjectRoot(root, () => buildCanvasDataModel(relations));
|
|
58155
|
+
const readable = new Set(listed.map((r) => r.id));
|
|
57824
58156
|
const members = new Set(listFamilyRecords(cfg.dataDir, projectId).filter((r) => r.parentProjectId !== void 0).map((r) => r.id));
|
|
57825
|
-
|
|
58157
|
+
const hostedOf = externalRecords(runWithProjectRoot(root, () => loadProjectConfig()));
|
|
58158
|
+
return withProjectLinks(model, (node) => {
|
|
58159
|
+
const id = node.external ? hostedOf.get(node.id) : node.id;
|
|
58160
|
+
if (id === void 0 || !readable.has(id)) return void 0;
|
|
58161
|
+
return node.external || members.has(id) ? id : void 0;
|
|
58162
|
+
});
|
|
57826
58163
|
}
|
|
57827
|
-
function
|
|
58164
|
+
function externalRecords(config) {
|
|
58165
|
+
const out = /* @__PURE__ */ new Map();
|
|
58166
|
+
for (const [alias, decl] of Object.entries(config?.externals ?? {})) {
|
|
58167
|
+
if (decl?.source?.hosted !== void 0) out.set(alias, decl.source.hosted);
|
|
58168
|
+
}
|
|
58169
|
+
return out;
|
|
58170
|
+
}
|
|
58171
|
+
function withProjectLinks(model, linkOf) {
|
|
57828
58172
|
const m = model;
|
|
57829
58173
|
if (!Array.isArray(m.subsystems)) return model;
|
|
57830
58174
|
return {
|
|
57831
58175
|
...model,
|
|
57832
|
-
subsystems: m.subsystems.map((s) =>
|
|
58176
|
+
subsystems: m.subsystems.map((s) => {
|
|
58177
|
+
const recordId = s.project ? linkOf(s) : void 0;
|
|
58178
|
+
return recordId !== void 0 ? { ...s, recordId } : s;
|
|
58179
|
+
})
|
|
57833
58180
|
};
|
|
57834
58181
|
}
|
|
57835
58182
|
function getWebProjectExternals(cfg, sessionId, projectId) {
|
|
@@ -64200,7 +64547,7 @@ var variantsCmd = program.command("variants").description("Component variants \u
|
|
|
64200
64547
|
variantsCmd.command("list").alias("ls").description("List the component variants in the registry (global + project) with their base and guidance").action(async () => {
|
|
64201
64548
|
await runVariants();
|
|
64202
64549
|
});
|
|
64203
|
-
program.command("diagram").description("Generate architecture diagrams from the spec tree \u2014 interactive canvas by default; Mermaid / draw.io / Excalidraw via --format").option("--subsystem <id>", "scope the component diagram to one subsystem (plus its external neighbors)").option("--sequence <component:method>", "emit a sequence diagram derived from the method's L5 narrative").option("--depth <n>", "max call-expansion depth for sequence diagrams (default 3)", (v) => parseInt(v, 10)).option("--all", "write the full diagram set: system, per-subsystem, entrypoint sequences, and the interactive canvas").option("--canvas", "emit the interactive self-contained HTML canvas (pan/zoom, collapse boundaries, detail panel, issue overlay)").option("--drawio", "emit an editable draw.io / diagrams.net file (same layout as the canvas)").option("--excalidraw", "emit an editable Excalidraw scene (same layout as the canvas)").option("--format <fmt>", "alias for the flags above: mermaid | canvas | drawio | excalidraw").option("--out <path>", "write to a file (or directory with --all; default .wai/docs/diagrams) instead of stdout").action(async (opts) => {
|
|
64550
|
+
program.command("diagram").description("Generate architecture diagrams from the spec tree \u2014 interactive canvas by default; Mermaid / draw.io / Excalidraw via --format").option("--subsystem <id>", "scope the component diagram to one subsystem (plus its external neighbors)").option("--sequence <component:method>", "emit a sequence diagram derived from the method's L5 narrative").option("--depth <n>", "max call-expansion depth for sequence diagrams (default 3)", (v) => parseInt(v, 10)).option("--all", "write the full diagram set: system, per-subsystem, entrypoint sequences, and the interactive canvas").option("--canvas", "emit the interactive self-contained HTML canvas (pan/zoom, collapse boundaries, detail panel, issue overlay)").option("--drawio", "emit an editable draw.io / diagrams.net file (same layout as the canvas)").option("--excalidraw", "emit an editable Excalidraw scene (same layout as the canvas)").option("--format <fmt>", "alias for the flags above: mermaid | canvas | drawio | excalidraw").option("--out <path>", "write to a file (or directory with --all; default .wai/docs/diagrams) instead of stdout").option("--no-health", "skip comparing each consumption relation with its live producer: the canvas draws those edges as not checked").action(async (opts) => {
|
|
64204
64551
|
await runDiagram({
|
|
64205
64552
|
subsystem: opts.subsystem,
|
|
64206
64553
|
sequence: opts.sequence,
|
|
@@ -64210,7 +64557,8 @@ program.command("diagram").description("Generate architecture diagrams from the
|
|
|
64210
64557
|
drawio: opts.drawio,
|
|
64211
64558
|
excalidraw: opts.excalidraw,
|
|
64212
64559
|
format: opts.format,
|
|
64213
|
-
out: opts.out
|
|
64560
|
+
out: opts.out,
|
|
64561
|
+
health: opts.health
|
|
64214
64562
|
});
|
|
64215
64563
|
});
|
|
64216
64564
|
program.command("list").alias("ls").description("List all dynamic agents resolved from the spec tree").action(async () => {
|