@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/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 {
|
|
@@ -25906,6 +26217,16 @@ function identityFieldsOf(field) {
|
|
|
25906
26217
|
return ["name", "id"];
|
|
25907
26218
|
}
|
|
25908
26219
|
}
|
|
26220
|
+
function withoutAbsentFields(value) {
|
|
26221
|
+
if (Array.isArray(value)) return value.map(withoutAbsentFields);
|
|
26222
|
+
if (typeof value !== "object" || value === null) return value;
|
|
26223
|
+
const out = {};
|
|
26224
|
+
for (const [key, field] of Object.entries(value)) {
|
|
26225
|
+
if (field === void 0 || field === null) continue;
|
|
26226
|
+
out[key] = withoutAbsentFields(field);
|
|
26227
|
+
}
|
|
26228
|
+
return out;
|
|
26229
|
+
}
|
|
25909
26230
|
function carriesEditMarker(item) {
|
|
25910
26231
|
if (item === null || typeof item !== "object") return false;
|
|
25911
26232
|
const o = item;
|
|
@@ -28884,7 +29205,7 @@ var init_specs2 = __esm({
|
|
|
28884
29205
|
return out;
|
|
28885
29206
|
};
|
|
28886
29207
|
const unsetFields = Array.isArray(delta.unset) ? delta.unset.filter((f) => typeof f === "string") : [];
|
|
28887
|
-
const { unset: _unset, ...mergeableDelta } = delta;
|
|
29208
|
+
const { unset: _unset, ...mergeableDelta } = withoutAbsentFields(delta);
|
|
28888
29209
|
const storedBefore = JSON.parse(JSON.stringify(result));
|
|
28889
29210
|
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
28890
29211
|
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
@@ -42918,7 +43239,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
42918
43239
|
inputSchema: {
|
|
42919
43240
|
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})"),
|
|
42920
43241
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
42921
|
-
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.`),
|
|
43242
|
+
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.`),
|
|
42922
43243
|
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.")
|
|
42923
43244
|
},
|
|
42924
43245
|
outputSchema: specChangeReportOutput
|
|
@@ -43230,7 +43551,10 @@ var init_server = __esm({
|
|
|
43230
43551
|
stale: import_zod11.z.boolean(),
|
|
43231
43552
|
drifted: import_zod11.z.boolean().optional(),
|
|
43232
43553
|
uses: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())),
|
|
43233
|
-
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.")
|
|
43234
43558
|
})).describe("One status per declared external, in declaration order; EXTERNAL_CHECK_UNAVAILABLE marks what could not be compared."),
|
|
43235
43559
|
...staleServerOutput
|
|
43236
43560
|
};
|
|
@@ -47103,12 +47427,16 @@ function parseSequenceRef(ref) {
|
|
|
47103
47427
|
}
|
|
47104
47428
|
return { component: ref.slice(0, sep18), method: ref.slice(sep18 + 1) };
|
|
47105
47429
|
}
|
|
47430
|
+
function canvasRelations(options) {
|
|
47431
|
+
if (options.health === false) return void 0;
|
|
47432
|
+
return familyRelations2();
|
|
47433
|
+
}
|
|
47106
47434
|
async function runDiagram(rawOptions = {}) {
|
|
47107
47435
|
assertProjectInitialized();
|
|
47108
47436
|
const options = applyFormat(rawOptions);
|
|
47109
47437
|
if (options.canvas && !options.all) {
|
|
47110
47438
|
const dest2 = options.out ?? path58.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
47111
|
-
writeArtifact(dest2, renderDiagram("canvas"));
|
|
47439
|
+
writeArtifact(dest2, renderDiagram("canvas", canvasRelations(options)));
|
|
47112
47440
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
47113
47441
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline).");
|
|
47114
47442
|
return;
|
|
@@ -47130,7 +47458,7 @@ async function runDiagram(rawOptions = {}) {
|
|
|
47130
47458
|
const wantsMermaid = options.format?.toLowerCase().startsWith("mermaid") || !!options.subsystem || !!options.sequence;
|
|
47131
47459
|
if (!options.all && !options.sequence && !wantsMermaid) {
|
|
47132
47460
|
const dest2 = options.out ?? path58.join(AI_PATHS.docsDir(), "diagrams", "canvas.html");
|
|
47133
|
-
writeArtifact(dest2, renderDiagram("canvas"));
|
|
47461
|
+
writeArtifact(dest2, renderDiagram("canvas", canvasRelations(options)));
|
|
47134
47462
|
logger.success(`Interactive canvas written to ${dest2}`);
|
|
47135
47463
|
logger.info("Open it in a browser \u2014 fully self-contained (works offline). Other formats: --format mermaid|drawio|excalidraw.");
|
|
47136
47464
|
return;
|
|
@@ -47145,7 +47473,7 @@ async function runDiagram(rawOptions = {}) {
|
|
|
47145
47473
|
for (const file of files) {
|
|
47146
47474
|
writeArtifact(path58.join(outDir, file.relPath), toMarkdown(file));
|
|
47147
47475
|
}
|
|
47148
|
-
writeArtifact(path58.join(outDir, "canvas.html"), renderDiagram("canvas"));
|
|
47476
|
+
writeArtifact(path58.join(outDir, "canvas.html"), renderDiagram("canvas", canvasRelations(options)));
|
|
47149
47477
|
writeArtifact(path58.join(outDir, "architecture.drawio"), renderDiagram("drawio"));
|
|
47150
47478
|
writeArtifact(path58.join(outDir, "architecture.excalidraw"), renderDiagram("excalidraw"));
|
|
47151
47479
|
const graph2 = loadSpecGraph();
|
|
@@ -51458,6 +51786,7 @@ init_validation();
|
|
|
51458
51786
|
init_errors();
|
|
51459
51787
|
init_validation();
|
|
51460
51788
|
init_validation();
|
|
51789
|
+
init_validation();
|
|
51461
51790
|
function validateAsComplete2() {
|
|
51462
51791
|
const config = loadProjectConfig();
|
|
51463
51792
|
if (!config) throw new ProjectNotInitializedError();
|
|
@@ -57229,20 +57558,31 @@ function getSnapshot2(dataDir, snapshotId) {
|
|
|
57229
57558
|
function getSnapshotArtifact(dataDir, snapshotId, kind, portalId) {
|
|
57230
57559
|
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).getArtifact(snapshotId, kind, portalId);
|
|
57231
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
|
+
}
|
|
57232
57570
|
function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
57233
57571
|
const root = resolveProjectRoot(dataDir, principal, projectId);
|
|
57234
57572
|
if (!root) throw new ForbiddenError("project not authorized or unknown");
|
|
57573
|
+
const relations = runWithProjectBinding(root, creatorReach(dataDir, principal, projectId, root), () => familyRelations2());
|
|
57235
57574
|
return runWithProjectRoot(root, () => {
|
|
57236
57575
|
const snapshot = {
|
|
57237
57576
|
id: "",
|
|
57238
57577
|
projectId,
|
|
57239
57578
|
view,
|
|
57240
57579
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57241
|
-
//
|
|
57242
|
-
|
|
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))
|
|
57243
57583
|
};
|
|
57244
57584
|
if (artifacts.includes("html")) {
|
|
57245
|
-
snapshot.html = renderDiagram("canvas");
|
|
57585
|
+
snapshot.html = renderDiagram("canvas", relations);
|
|
57246
57586
|
}
|
|
57247
57587
|
if (artifacts.includes("openapi")) {
|
|
57248
57588
|
const result = exportSurface2("project", "openapi");
|
|
@@ -57809,17 +58149,34 @@ function getWebProjectCanvasModel(cfg, sessionId, projectId) {
|
|
|
57809
58149
|
if (!root) {
|
|
57810
58150
|
throw new ForbiddenError("project not authorized or unknown");
|
|
57811
58151
|
}
|
|
57812
|
-
const
|
|
57813
|
-
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));
|
|
57814
58156
|
const members = new Set(listFamilyRecords(cfg.dataDir, projectId).filter((r) => r.parentProjectId !== void 0).map((r) => r.id));
|
|
57815
|
-
|
|
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
|
+
});
|
|
58163
|
+
}
|
|
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;
|
|
57816
58170
|
}
|
|
57817
|
-
function
|
|
58171
|
+
function withProjectLinks(model, linkOf) {
|
|
57818
58172
|
const m = model;
|
|
57819
58173
|
if (!Array.isArray(m.subsystems)) return model;
|
|
57820
58174
|
return {
|
|
57821
58175
|
...model,
|
|
57822
|
-
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
|
+
})
|
|
57823
58180
|
};
|
|
57824
58181
|
}
|
|
57825
58182
|
function getWebProjectExternals(cfg, sessionId, projectId) {
|
|
@@ -64190,7 +64547,7 @@ var variantsCmd = program.command("variants").description("Component variants \u
|
|
|
64190
64547
|
variantsCmd.command("list").alias("ls").description("List the component variants in the registry (global + project) with their base and guidance").action(async () => {
|
|
64191
64548
|
await runVariants();
|
|
64192
64549
|
});
|
|
64193
|
-
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) => {
|
|
64194
64551
|
await runDiagram({
|
|
64195
64552
|
subsystem: opts.subsystem,
|
|
64196
64553
|
sequence: opts.sequence,
|
|
@@ -64200,7 +64557,8 @@ program.command("diagram").description("Generate architecture diagrams from the
|
|
|
64200
64557
|
drawio: opts.drawio,
|
|
64201
64558
|
excalidraw: opts.excalidraw,
|
|
64202
64559
|
format: opts.format,
|
|
64203
|
-
out: opts.out
|
|
64560
|
+
out: opts.out,
|
|
64561
|
+
health: opts.health
|
|
64204
64562
|
});
|
|
64205
64563
|
});
|
|
64206
64564
|
program.command("list").alias("ls").description("List all dynamic agents resolved from the spec tree").action(async () => {
|