@a9n-shoji/rvw 0.4.3 → 0.5.0

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.mjs CHANGED
@@ -20295,7 +20295,7 @@ import path5 from "node:path";
20295
20295
  import { TextDecoder as TextDecoder2 } from "node:util";
20296
20296
 
20297
20297
  // src/shared/constants.ts
20298
- var APP_VERSION = "0.4.3";
20298
+ var APP_VERSION = "0.5.0";
20299
20299
  var PROTOCOL_VERSION = 4;
20300
20300
  var VIEWER_ID_HEADER = "x-rvw-viewer-id";
20301
20301
  var VIEWER_OPEN_LEASE_HEADER = "x-rvw-viewer-open-lease";
@@ -43805,6 +43805,691 @@ function createRuntime(options = {}) {
43805
43805
  return { database, git, github, service, close: () => database.close() };
43806
43806
  }
43807
43807
 
43808
+ // src/domain/structure-projection.ts
43809
+ var STRUCTURE_NODE_WIDTH = 228;
43810
+ var STRUCTURE_NODE_HEIGHT = 112;
43811
+ function stableCompare(left, right) {
43812
+ return left === right ? 0 : left < right ? -1 : 1;
43813
+ }
43814
+ function compareLinks([leftA, leftB], [rightA, rightB]) {
43815
+ const first = stableCompare(leftA, rightA);
43816
+ return first === 0 ? stableCompare(leftB, rightB) : first;
43817
+ }
43818
+ function simpleStructureTopology(structure) {
43819
+ const neighborSets = new Map(structure.nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
43820
+ const pairNodes = /* @__PURE__ */ new Map();
43821
+ const pairDirections = /* @__PURE__ */ new Map();
43822
+ for (const edge of structure.edges) {
43823
+ if (edge.from === edge.to || !neighborSets.has(edge.from) || !neighborSets.has(edge.to)) {
43824
+ continue;
43825
+ }
43826
+ neighborSets.get(edge.from)?.add(edge.to);
43827
+ neighborSets.get(edge.to)?.add(edge.from);
43828
+ const ordered = [edge.from, edge.to].sort(stableCompare);
43829
+ const pairKey = JSON.stringify(ordered);
43830
+ pairNodes.set(pairKey, ordered);
43831
+ const directions = pairDirections.get(pairKey) ?? {
43832
+ hasUndirected: false,
43833
+ directions: /* @__PURE__ */ new Map()
43834
+ };
43835
+ if (edge.directed) {
43836
+ directions.directions.set(JSON.stringify([edge.from, edge.to]), [edge.from, edge.to]);
43837
+ } else {
43838
+ directions.hasUndirected = true;
43839
+ }
43840
+ pairDirections.set(pairKey, directions);
43841
+ }
43842
+ return {
43843
+ neighbors: new Map(
43844
+ [...neighborSets].map(([nodeId, neighbors]) => [nodeId, [...neighbors].sort(stableCompare)])
43845
+ ),
43846
+ links: [...pairNodes.values()].sort(compareLinks),
43847
+ directionalLinks: [...pairDirections.values()].flatMap(
43848
+ ({ hasUndirected, directions }) => !hasUndirected && directions.size === 1 ? [...directions.values()] : []
43849
+ ).sort(compareLinks)
43850
+ };
43851
+ }
43852
+ function topologyComponents(topology) {
43853
+ const components = [];
43854
+ const assigned = /* @__PURE__ */ new Set();
43855
+ for (const first of [...topology.neighbors.keys()].sort(stableCompare)) {
43856
+ if (assigned.has(first)) continue;
43857
+ const component = [];
43858
+ const queue = [first];
43859
+ assigned.add(first);
43860
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
43861
+ const current = queue[index2];
43862
+ component.push(current);
43863
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
43864
+ if (assigned.has(neighbor)) continue;
43865
+ assigned.add(neighbor);
43866
+ queue.push(neighbor);
43867
+ }
43868
+ }
43869
+ component.sort(stableCompare);
43870
+ components.push(component);
43871
+ }
43872
+ return components.sort(
43873
+ (left, right) => right.length - left.length || stableCompare(left[0], right[0])
43874
+ );
43875
+ }
43876
+ function topologyRoot(nodeIds, topology) {
43877
+ const neighborDegree = (nodeId) => (topology.neighbors.get(nodeId) ?? []).reduce(
43878
+ (total, neighbor) => total + (topology.neighbors.get(neighbor)?.length ?? 0),
43879
+ 0
43880
+ );
43881
+ return [...nodeIds].sort(
43882
+ (left, right) => (topology.neighbors.get(right)?.length ?? 0) - (topology.neighbors.get(left)?.length ?? 0) || neighborDegree(right) - neighborDegree(left) || stableCompare(left, right)
43883
+ )[0];
43884
+ }
43885
+ function countNonForwardLinks(ranks, directionalLinks) {
43886
+ return directionalLinks.reduce((count, [from, to]) => {
43887
+ const fromRank = ranks.get(from);
43888
+ const toRank = ranks.get(to);
43889
+ return count + (fromRank !== void 0 && toRank !== void 0 && fromRank >= toRank ? 1 : 0);
43890
+ }, 0);
43891
+ }
43892
+ function normalizedRankIndexes(ranks) {
43893
+ return new Map(
43894
+ [...new Set(ranks.values())].sort((left, right) => left - right).map((rank, index2) => [rank, index2])
43895
+ );
43896
+ }
43897
+ function totalDirectionalSpan(ranks, directionalLinks) {
43898
+ const indexes = normalizedRankIndexes(ranks);
43899
+ return directionalLinks.reduce((total, [from, to]) => {
43900
+ const fromRank = ranks.get(from);
43901
+ const toRank = ranks.get(to);
43902
+ if (fromRank === void 0 || toRank === void 0) return total;
43903
+ return total + Math.abs(indexes.get(fromRank) - indexes.get(toRank));
43904
+ }, 0);
43905
+ }
43906
+ function compareNumberArrays(left, right) {
43907
+ for (let index2 = 0; index2 < Math.max(left.length, right.length); index2 += 1) {
43908
+ const difference = (left[index2] ?? 0) - (right[index2] ?? 0);
43909
+ if (difference !== 0) return difference;
43910
+ }
43911
+ return 0;
43912
+ }
43913
+ function canonicalTopologyStructuralColors(nodeIds, topology, originNodeId) {
43914
+ const nodeSet = new Set(nodeIds);
43915
+ const incomingNeighbors = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
43916
+ const outgoingNeighbors = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
43917
+ for (const [from, to] of topology.directionalLinks) {
43918
+ if (!nodeSet.has(from) || !nodeSet.has(to)) continue;
43919
+ outgoingNeighbors.get(from)?.add(to);
43920
+ incomingNeighbors.get(to)?.add(from);
43921
+ }
43922
+ const ambiguousNeighbors = new Map(
43923
+ nodeIds.map((nodeId) => {
43924
+ const directionalNeighbors = /* @__PURE__ */ new Set([
43925
+ ...incomingNeighbors.get(nodeId),
43926
+ ...outgoingNeighbors.get(nodeId)
43927
+ ]);
43928
+ return [
43929
+ nodeId,
43930
+ new Set(
43931
+ (topology.neighbors.get(nodeId) ?? []).filter(
43932
+ (neighbor) => nodeSet.has(neighbor) && !directionalNeighbors.has(neighbor)
43933
+ )
43934
+ )
43935
+ ];
43936
+ })
43937
+ );
43938
+ const assignColors = (descriptors) => {
43939
+ const colorByDescriptor = new Map(
43940
+ [...new Set(descriptors.values())].sort(stableCompare).map((descriptor, index2) => [descriptor, index2])
43941
+ );
43942
+ return new Map(
43943
+ nodeIds.map((nodeId) => [nodeId, colorByDescriptor.get(descriptors.get(nodeId))])
43944
+ );
43945
+ };
43946
+ let colors = assignColors(
43947
+ new Map(
43948
+ nodeIds.map((nodeId) => [
43949
+ nodeId,
43950
+ JSON.stringify([
43951
+ nodeId === originNodeId ? 1 : 0,
43952
+ incomingNeighbors.get(nodeId).size,
43953
+ outgoingNeighbors.get(nodeId).size,
43954
+ ambiguousNeighbors.get(nodeId).size
43955
+ ])
43956
+ ])
43957
+ )
43958
+ );
43959
+ for (let pass = 0; pass < nodeIds.length; pass += 1) {
43960
+ const next2 = assignColors(
43961
+ new Map(
43962
+ nodeIds.map((nodeId) => [
43963
+ nodeId,
43964
+ JSON.stringify([
43965
+ colors.get(nodeId),
43966
+ [...incomingNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right),
43967
+ [...outgoingNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right),
43968
+ [...ambiguousNeighbors.get(nodeId)].map((neighbor) => colors.get(neighbor)).sort((left, right) => left - right)
43969
+ ])
43970
+ ])
43971
+ )
43972
+ );
43973
+ if (nodeIds.every((nodeId) => next2.get(nodeId) === colors.get(nodeId))) break;
43974
+ colors = next2;
43975
+ }
43976
+ return colors;
43977
+ }
43978
+ function chooseRankProposal(nodeId, proposals, ranks, directionalLinks) {
43979
+ const scored = [...proposals].map((rank) => {
43980
+ const candidate = new Map(ranks).set(nodeId, rank);
43981
+ return {
43982
+ rank,
43983
+ nonForward: countNonForwardLinks(candidate, directionalLinks),
43984
+ occupiedColumns: new Set(candidate.values()).size,
43985
+ span: totalDirectionalSpan(candidate, directionalLinks)
43986
+ };
43987
+ });
43988
+ scored.sort(
43989
+ (left, right) => left.nonForward - right.nonForward || left.occupiedColumns - right.occupiedColumns || left.span - right.span || Math.abs(left.rank) - Math.abs(right.rank) || left.rank - right.rank
43990
+ );
43991
+ return scored[0].rank;
43992
+ }
43993
+ function applyForwardWaves(nodeSet, topology, ranks) {
43994
+ for (let pass = 0; pass < nodeSet.size; pass += 1) {
43995
+ const proposals = /* @__PURE__ */ new Map();
43996
+ for (const [from, to] of topology.directionalLinks) {
43997
+ if (!nodeSet.has(from) || !nodeSet.has(to) || ranks.has(to)) continue;
43998
+ const fromRank = ranks.get(from);
43999
+ if (fromRank === void 0) continue;
44000
+ const targetProposals = proposals.get(to) ?? /* @__PURE__ */ new Set();
44001
+ targetProposals.add(fromRank + 1);
44002
+ proposals.set(to, targetProposals);
44003
+ }
44004
+ if (proposals.size === 0) break;
44005
+ const accepted = [...proposals].map(
44006
+ ([nodeId, nodeProposals]) => [
44007
+ nodeId,
44008
+ chooseRankProposal(nodeId, nodeProposals, ranks, topology.directionalLinks)
44009
+ ]
44010
+ ).sort(([left], [right]) => stableCompare(left, right));
44011
+ for (const [nodeId, rank] of accepted) ranks.set(nodeId, rank);
44012
+ }
44013
+ }
44014
+ function stronglyConnectedComponents(nodeIds, directionalLinks) {
44015
+ const nodeSet = new Set(nodeIds);
44016
+ const outgoing = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44017
+ for (const [from, to] of directionalLinks) {
44018
+ if (nodeSet.has(from) && nodeSet.has(to)) outgoing.get(from)?.add(to);
44019
+ }
44020
+ const indexes = /* @__PURE__ */ new Map();
44021
+ const lowLinks = /* @__PURE__ */ new Map();
44022
+ const stack = [];
44023
+ const onStack = /* @__PURE__ */ new Set();
44024
+ const components = [];
44025
+ let nextIndex = 0;
44026
+ const visit = (nodeId) => {
44027
+ const nodeIndex = nextIndex;
44028
+ nextIndex += 1;
44029
+ indexes.set(nodeId, nodeIndex);
44030
+ lowLinks.set(nodeId, nodeIndex);
44031
+ stack.push(nodeId);
44032
+ onStack.add(nodeId);
44033
+ for (const neighbor of [...outgoing.get(nodeId) ?? []].sort(stableCompare)) {
44034
+ if (!indexes.has(neighbor)) {
44035
+ visit(neighbor);
44036
+ lowLinks.set(nodeId, Math.min(lowLinks.get(nodeId), lowLinks.get(neighbor)));
44037
+ } else if (onStack.has(neighbor)) {
44038
+ lowLinks.set(nodeId, Math.min(lowLinks.get(nodeId), indexes.get(neighbor)));
44039
+ }
44040
+ }
44041
+ if (lowLinks.get(nodeId) !== indexes.get(nodeId)) return;
44042
+ const component = [];
44043
+ for (; ; ) {
44044
+ const member = stack.pop();
44045
+ onStack.delete(member);
44046
+ component.push(member);
44047
+ if (member === nodeId) break;
44048
+ }
44049
+ component.sort(stableCompare);
44050
+ components.push(component);
44051
+ };
44052
+ for (const nodeId of [...nodeIds].sort(stableCompare)) {
44053
+ if (!indexes.has(nodeId)) visit(nodeId);
44054
+ }
44055
+ return components;
44056
+ }
44057
+ function internalComponentRanks(nodeIds, directionalLinks, structuralColors, preferredAnchor, originIncomingBoundaryNodeIds, incomingBoundaryNodeIds, outgoingBoundaryNodeIds, attachmentBoundaryNodeIds) {
44058
+ if (nodeIds.length === 1) return /* @__PURE__ */ new Map([[nodeIds[0], 0]]);
44059
+ const nodeSet = new Set(nodeIds);
44060
+ const internalLinks = directionalLinks.filter(
44061
+ ([from, to]) => nodeSet.has(from) && nodeSet.has(to)
44062
+ );
44063
+ const neighborSets = new Map(nodeIds.map((nodeId) => [nodeId, /* @__PURE__ */ new Set()]));
44064
+ for (const [from, to] of internalLinks) {
44065
+ neighborSets.get(from)?.add(to);
44066
+ neighborSets.get(to)?.add(from);
44067
+ }
44068
+ const topology = {
44069
+ neighbors: new Map(
44070
+ [...neighborSets].map(([nodeId, neighbors]) => [nodeId, [...neighbors].sort(stableCompare)])
44071
+ ),
44072
+ links: internalLinks,
44073
+ directionalLinks: internalLinks
44074
+ };
44075
+ const ranksFromAnchor = (anchor) => {
44076
+ const rawRanks = /* @__PURE__ */ new Map([[anchor, 0]]);
44077
+ applyForwardWaves(nodeSet, topology, rawRanks);
44078
+ const rankIndexes = normalizedRankIndexes(rawRanks);
44079
+ return new Map(nodeIds.map((nodeId) => [nodeId, rankIndexes.get(rawRanks.get(nodeId)) ?? 0]));
44080
+ };
44081
+ if (preferredAnchor && nodeSet.has(preferredAnchor)) return ranksFromAnchor(preferredAnchor);
44082
+ const candidates = nodeIds.map((anchor) => {
44083
+ const ranks = ranksFromAnchor(anchor);
44084
+ const maxRank = Math.max(...ranks.values());
44085
+ const rankLoadProfile = Array.from(
44086
+ { length: maxRank + 1 },
44087
+ (_, rank) => nodeIds.filter((nodeId) => ranks.get(nodeId) === rank).length
44088
+ );
44089
+ const rankStructuralProfile = rankLoadProfile.flatMap((_, rank) => [
44090
+ -1,
44091
+ ...nodeIds.filter((nodeId) => ranks.get(nodeId) === rank).map((nodeId) => structuralColors.get(nodeId)).sort((left, right) => left - right)
44092
+ ]);
44093
+ return {
44094
+ anchor,
44095
+ ranks,
44096
+ nonForward: countNonForwardLinks(ranks, internalLinks),
44097
+ originIncomingBoundaryRanks: originIncomingBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44098
+ incomingBoundaryRanks: incomingBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44099
+ outgoingBoundaryDistances: outgoingBoundaryNodeIds.map((nodeId) => maxRank - ranks.get(nodeId)).sort((left, right) => right - left),
44100
+ attachmentBoundaryRanks: attachmentBoundaryNodeIds.map((nodeId) => ranks.get(nodeId)).sort((left, right) => right - left),
44101
+ width: new Set(ranks.values()).size,
44102
+ span: totalDirectionalSpan(ranks, internalLinks),
44103
+ rankLoadProfile,
44104
+ rankStructuralProfile,
44105
+ anchorStructuralColor: structuralColors.get(anchor)
44106
+ };
44107
+ });
44108
+ candidates.sort(
44109
+ (left, right) => left.nonForward - right.nonForward || compareNumberArrays(left.originIncomingBoundaryRanks, right.originIncomingBoundaryRanks) || compareNumberArrays(left.incomingBoundaryRanks, right.incomingBoundaryRanks) || compareNumberArrays(left.outgoingBoundaryDistances, right.outgoingBoundaryDistances) || compareNumberArrays(left.attachmentBoundaryRanks, right.attachmentBoundaryRanks) || left.width - right.width || left.span - right.span || compareNumberArrays(left.rankLoadProfile, right.rankLoadProfile) || compareNumberArrays(left.rankStructuralProfile, right.rankStructuralProfile) || left.anchorStructuralColor - right.anchorStructuralColor || stableCompare(left.anchor, right.anchor)
44110
+ );
44111
+ return candidates[0].ranks;
44112
+ }
44113
+ function directionalCondensationRanks(nodeIds, topology, originNodeId) {
44114
+ const nodeSet = new Set(nodeIds);
44115
+ const directionalLinks = topology.directionalLinks.filter(
44116
+ ([from, to]) => nodeSet.has(from) && nodeSet.has(to)
44117
+ );
44118
+ const structuralColors = canonicalTopologyStructuralColors(nodeIds, topology, originNodeId);
44119
+ const components = stronglyConnectedComponents(nodeIds, directionalLinks);
44120
+ const componentByNodeId = /* @__PURE__ */ new Map();
44121
+ components.forEach((component, componentIndex) => {
44122
+ for (const nodeId of component) componentByNodeId.set(nodeId, componentIndex);
44123
+ });
44124
+ const outgoing = new Map(components.map((_, index2) => [index2, /* @__PURE__ */ new Set()]));
44125
+ const incoming = new Map(components.map((_, index2) => [index2, /* @__PURE__ */ new Set()]));
44126
+ for (const [from, to] of directionalLinks) {
44127
+ const fromComponent = componentByNodeId.get(from);
44128
+ const toComponent = componentByNodeId.get(to);
44129
+ if (fromComponent === toComponent) continue;
44130
+ outgoing.get(fromComponent).add(toComponent);
44131
+ incoming.get(toComponent).add(fromComponent);
44132
+ }
44133
+ const originComponent = componentByNodeId.get(originNodeId);
44134
+ const directionalGroups = [];
44135
+ const directionalGroupByComponent = /* @__PURE__ */ new Map();
44136
+ for (let first = 0; first < components.length; first += 1) {
44137
+ if (directionalGroupByComponent.has(first)) continue;
44138
+ const groupIndex = directionalGroups.length;
44139
+ const group = [];
44140
+ const queue = [first];
44141
+ directionalGroupByComponent.set(first, groupIndex);
44142
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
44143
+ const current = queue[index2];
44144
+ group.push(current);
44145
+ for (const neighbor of [...outgoing.get(current), ...incoming.get(current)]) {
44146
+ if (directionalGroupByComponent.has(neighbor)) continue;
44147
+ directionalGroupByComponent.set(neighbor, groupIndex);
44148
+ queue.push(neighbor);
44149
+ }
44150
+ }
44151
+ directionalGroups.push(group);
44152
+ }
44153
+ const directionalGroupByNodeId = /* @__PURE__ */ new Map();
44154
+ for (const [nodeId, componentIndex] of componentByNodeId) {
44155
+ directionalGroupByNodeId.set(nodeId, directionalGroupByComponent.get(componentIndex));
44156
+ }
44157
+ const attachmentBoundaryNodeIds = new Map(
44158
+ components.map((_, componentIndex) => [componentIndex, []])
44159
+ );
44160
+ for (const [left, right] of topology.links) {
44161
+ if (directionalGroupByNodeId.get(left) === directionalGroupByNodeId.get(right)) continue;
44162
+ attachmentBoundaryNodeIds.get(componentByNodeId.get(left)).push(left);
44163
+ attachmentBoundaryNodeIds.get(componentByNodeId.get(right)).push(right);
44164
+ }
44165
+ const compareComponents = (left, right) => stableCompare(components[left][0], components[right][0]);
44166
+ const localRanks = /* @__PURE__ */ new Map();
44167
+ const componentWidths = /* @__PURE__ */ new Map();
44168
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex += 1) {
44169
+ const component = components[componentIndex];
44170
+ const ranks2 = internalComponentRanks(
44171
+ component,
44172
+ directionalLinks,
44173
+ structuralColors,
44174
+ component.includes(originNodeId) ? originNodeId : null,
44175
+ directionalLinks.flatMap(
44176
+ ([from, to]) => componentByNodeId.get(from) === originComponent && componentByNodeId.get(to) === componentIndex && componentIndex !== originComponent ? [to] : []
44177
+ ),
44178
+ directionalLinks.flatMap(
44179
+ ([from, to]) => componentByNodeId.get(to) === componentIndex && componentByNodeId.get(from) !== componentIndex ? [to] : []
44180
+ ),
44181
+ directionalLinks.flatMap(
44182
+ ([from, to]) => componentByNodeId.get(from) === componentIndex && componentByNodeId.get(to) !== componentIndex ? [from] : []
44183
+ ),
44184
+ attachmentBoundaryNodeIds.get(componentIndex)
44185
+ );
44186
+ localRanks.set(componentIndex, ranks2);
44187
+ componentWidths.set(componentIndex, Math.max(...ranks2.values()) + 1);
44188
+ }
44189
+ const remainingIncoming = new Map(
44190
+ components.map((_, componentIndex) => [componentIndex, incoming.get(componentIndex).size])
44191
+ );
44192
+ const componentStarts = /* @__PURE__ */ new Map();
44193
+ const ready = components.map((_, componentIndex) => componentIndex).filter((componentIndex) => remainingIncoming.get(componentIndex) === 0).sort(compareComponents);
44194
+ for (const componentIndex of ready) componentStarts.set(componentIndex, 0);
44195
+ while (ready.length > 0) {
44196
+ const current = ready.shift();
44197
+ for (const target of [...outgoing.get(current)].sort(compareComponents)) {
44198
+ componentStarts.set(
44199
+ target,
44200
+ Math.max(
44201
+ componentStarts.get(target) ?? 0,
44202
+ componentStarts.get(current) + componentWidths.get(current)
44203
+ )
44204
+ );
44205
+ const unresolved = remainingIncoming.get(target) - 1;
44206
+ remainingIncoming.set(target, unresolved);
44207
+ if (unresolved === 0) {
44208
+ ready.push(target);
44209
+ ready.sort(compareComponents);
44210
+ }
44211
+ }
44212
+ }
44213
+ const unshiftedRanks = /* @__PURE__ */ new Map();
44214
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex += 1) {
44215
+ for (const [nodeId, localRank] of localRanks.get(componentIndex)) {
44216
+ unshiftedRanks.set(nodeId, componentStarts.get(componentIndex) + localRank);
44217
+ }
44218
+ }
44219
+ const originGroup = directionalGroupByComponent.get(originComponent);
44220
+ const groupOffsets = /* @__PURE__ */ new Map([[originGroup, -unshiftedRanks.get(originNodeId)]]);
44221
+ while (groupOffsets.size < directionalGroups.length) {
44222
+ const proposals = /* @__PURE__ */ new Map();
44223
+ for (const [left, right] of topology.links) {
44224
+ const leftGroup = directionalGroupByNodeId.get(left);
44225
+ const rightGroup = directionalGroupByNodeId.get(right);
44226
+ if (leftGroup === rightGroup) continue;
44227
+ const leftOffset = groupOffsets.get(leftGroup);
44228
+ const rightOffset = groupOffsets.get(rightGroup);
44229
+ if (leftOffset !== void 0 && rightOffset === void 0) {
44230
+ const offsets = proposals.get(rightGroup) ?? /* @__PURE__ */ new Set();
44231
+ offsets.add(unshiftedRanks.get(left) + leftOffset + 1 - unshiftedRanks.get(right));
44232
+ proposals.set(rightGroup, offsets);
44233
+ }
44234
+ if (leftOffset === void 0 && rightOffset !== void 0) {
44235
+ const offsets = proposals.get(leftGroup) ?? /* @__PURE__ */ new Set();
44236
+ offsets.add(unshiftedRanks.get(right) + rightOffset + 1 - unshiftedRanks.get(left));
44237
+ proposals.set(leftGroup, offsets);
44238
+ }
44239
+ }
44240
+ if (proposals.size === 0) break;
44241
+ const knownGroupOffsets = new Map(groupOffsets);
44242
+ const rankedValues = [...unshiftedRanks].flatMap(([nodeId, rank]) => {
44243
+ const offset = knownGroupOffsets.get(directionalGroupByNodeId.get(nodeId));
44244
+ return offset === void 0 ? [] : [rank + offset];
44245
+ });
44246
+ const acceptedOffsets = /* @__PURE__ */ new Map();
44247
+ for (const [groupIndex, proposedOffsets] of proposals) {
44248
+ const candidates = [...proposedOffsets].map((offset) => {
44249
+ const crossGroupSpan = topology.links.reduce((total, [left, right]) => {
44250
+ const leftGroup = directionalGroupByNodeId.get(left);
44251
+ const rightGroup = directionalGroupByNodeId.get(right);
44252
+ const leftOffset = leftGroup === groupIndex ? offset : knownGroupOffsets.get(leftGroup);
44253
+ const rightOffset = rightGroup === groupIndex ? offset : knownGroupOffsets.get(rightGroup);
44254
+ if (leftOffset === void 0 || rightOffset === void 0) return total;
44255
+ return total + Math.abs(
44256
+ unshiftedRanks.get(left) + leftOffset - unshiftedRanks.get(right) - rightOffset
44257
+ );
44258
+ }, 0);
44259
+ const addedRanks = directionalGroups[groupIndex].flatMap(
44260
+ (componentIndex) => components[componentIndex].map((nodeId) => unshiftedRanks.get(nodeId) + offset)
44261
+ );
44262
+ return {
44263
+ offset,
44264
+ crossGroupSpan,
44265
+ occupiedColumns: (/* @__PURE__ */ new Set([...rankedValues, ...addedRanks])).size
44266
+ };
44267
+ });
44268
+ candidates.sort(
44269
+ (left, right) => left.crossGroupSpan - right.crossGroupSpan || left.occupiedColumns - right.occupiedColumns || Math.abs(left.offset) - Math.abs(right.offset) || left.offset - right.offset
44270
+ );
44271
+ acceptedOffsets.set(groupIndex, candidates[0].offset);
44272
+ }
44273
+ for (const [groupIndex, offset] of acceptedOffsets) groupOffsets.set(groupIndex, offset);
44274
+ }
44275
+ const ranks = /* @__PURE__ */ new Map();
44276
+ for (const [nodeId, rank] of unshiftedRanks) {
44277
+ const groupOffset = groupOffsets.get(directionalGroupByNodeId.get(nodeId));
44278
+ ranks.set(nodeId, rank + (groupOffset ?? 0));
44279
+ }
44280
+ return ranks;
44281
+ }
44282
+ function componentRanks(nodeIds, topology, entrypointId) {
44283
+ const nodeSet = new Set(nodeIds);
44284
+ const entrypoint = entrypointId && nodeSet.has(entrypointId) ? entrypointId : null;
44285
+ if (!entrypoint) {
44286
+ const root3 = topologyRoot(nodeIds, topology);
44287
+ const ranks2 = /* @__PURE__ */ new Map();
44288
+ ranks2.set(root3, 0);
44289
+ const queue = [root3];
44290
+ for (let index2 = 0; index2 < queue.length; index2 += 1) {
44291
+ const current = queue[index2];
44292
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
44293
+ if (!nodeSet.has(neighbor) || ranks2.has(neighbor)) continue;
44294
+ ranks2.set(neighbor, ranks2.get(current) + 1);
44295
+ queue.push(neighbor);
44296
+ }
44297
+ }
44298
+ return ranks2;
44299
+ }
44300
+ const ranks = directionalCondensationRanks(nodeIds, topology, entrypoint);
44301
+ const rankedQueue = [...ranks].sort(
44302
+ ([leftId, leftRank], [rightId, rightRank]) => leftRank - rightRank || stableCompare(leftId, rightId)
44303
+ ).map(([nodeId]) => nodeId);
44304
+ for (let index2 = 0; index2 < rankedQueue.length; index2 += 1) {
44305
+ const current = rankedQueue[index2];
44306
+ for (const neighbor of topology.neighbors.get(current) ?? []) {
44307
+ if (!nodeSet.has(neighbor) || ranks.has(neighbor)) continue;
44308
+ ranks.set(neighbor, ranks.get(current) + 1);
44309
+ rankedQueue.push(neighbor);
44310
+ }
44311
+ }
44312
+ return ranks;
44313
+ }
44314
+ function orderRankGroups(groups, topology) {
44315
+ const rankValues = [...groups.keys()].sort((left, right) => left - right);
44316
+ const result = new Map(
44317
+ rankValues.map((rank) => [rank, [...groups.get(rank)].sort(stableCompare)])
44318
+ );
44319
+ const sortAgainst = (rank, adjacentRank) => {
44320
+ const nodes = result.get(rank);
44321
+ const adjacent = result.get(adjacentRank);
44322
+ const adjacentOrder = new Map(adjacent.map((nodeId, index2) => [nodeId, index2]));
44323
+ const barycenter = (nodeId) => {
44324
+ const indexes = (topology.neighbors.get(nodeId) ?? []).flatMap((neighbor) => {
44325
+ const index2 = adjacentOrder.get(neighbor);
44326
+ return index2 === void 0 ? [] : [index2];
44327
+ });
44328
+ return indexes.length === 0 ? null : indexes.reduce((total, index2) => total + index2, 0) / indexes.length;
44329
+ };
44330
+ nodes.sort((left, right) => {
44331
+ const leftCenter = barycenter(left);
44332
+ const rightCenter = barycenter(right);
44333
+ if (leftCenter === null && rightCenter === null) return stableCompare(left, right);
44334
+ if (leftCenter === null) return 1;
44335
+ if (rightCenter === null) return -1;
44336
+ return leftCenter - rightCenter || stableCompare(left, right);
44337
+ });
44338
+ };
44339
+ for (let pass = 0; pass < 6; pass += 1) {
44340
+ for (let index2 = 1; index2 < rankValues.length; index2 += 1) {
44341
+ sortAgainst(rankValues[index2], rankValues[index2 - 1]);
44342
+ }
44343
+ for (let index2 = rankValues.length - 2; index2 >= 0; index2 -= 1) {
44344
+ sortAgainst(rankValues[index2], rankValues[index2 + 1]);
44345
+ }
44346
+ }
44347
+ return result;
44348
+ }
44349
+ function layoutTopologyComponent(nodeIds, topology, entrypointId) {
44350
+ const ranks = componentRanks(nodeIds, topology, entrypointId);
44351
+ const rawGroups = /* @__PURE__ */ new Map();
44352
+ for (const nodeId of nodeIds) {
44353
+ const rank = ranks.get(nodeId) ?? 0;
44354
+ const group = rawGroups.get(rank) ?? [];
44355
+ group.push(nodeId);
44356
+ rawGroups.set(rank, group);
44357
+ }
44358
+ const groups = orderRankGroups(rawGroups, topology);
44359
+ const rankValues = [...groups.keys()].sort((left, right) => left - right);
44360
+ const rankStride = STRUCTURE_NODE_WIDTH + 192;
44361
+ const rowStride = STRUCTURE_NODE_HEIGHT + 72;
44362
+ const maxRows = Math.max(...[...groups.values()].map((nodes) => nodes.length));
44363
+ const centerY = (maxRows - 1) * rowStride / 2;
44364
+ const positions = /* @__PURE__ */ new Map();
44365
+ rankValues.forEach((rank, rankIndex) => {
44366
+ const group = groups.get(rank);
44367
+ const top = centerY - (group.length - 1) * rowStride / 2;
44368
+ group.forEach((nodeId, rowIndex) => {
44369
+ positions.set(nodeId, { x: rankIndex * rankStride, y: top + rowIndex * rowStride });
44370
+ });
44371
+ });
44372
+ return {
44373
+ nodeIds,
44374
+ positions,
44375
+ ranks,
44376
+ width: (rankValues.length - 1) * rankStride + STRUCTURE_NODE_WIDTH,
44377
+ height: (maxRows - 1) * rowStride + STRUCTURE_NODE_HEIGHT
44378
+ };
44379
+ }
44380
+ function projectStructure(structure) {
44381
+ const topology = simpleStructureTopology(structure);
44382
+ if (structure.nodes.length === 0) {
44383
+ const diagnostics2 = {
44384
+ columnCount: 0,
44385
+ rowsPerColumn: [],
44386
+ maxRows: 0,
44387
+ directionalLinkCount: topology.directionalLinks.length,
44388
+ nonForwardDirectionalLinkCount: 0,
44389
+ nonForwardDirectionalLinkRatio: 0,
44390
+ originOutgoingDirectionalLinkCount: 0
44391
+ };
44392
+ return {
44393
+ positionsByNodeId: /* @__PURE__ */ new Map(),
44394
+ rankByNodeId: /* @__PURE__ */ new Map(),
44395
+ columnIndexByNodeId: /* @__PURE__ */ new Map(),
44396
+ columns: [],
44397
+ directionalLinks: topology.directionalLinks,
44398
+ diagnostics: diagnostics2
44399
+ };
44400
+ }
44401
+ const componentGap = 180;
44402
+ const outerPadding = 64;
44403
+ const components = topologyComponents(topology).map(
44404
+ (nodeIds) => layoutTopologyComponent(
44405
+ nodeIds,
44406
+ topology,
44407
+ nodeIds.includes(structure.originNodeId) ? structure.originNodeId : null
44408
+ )
44409
+ );
44410
+ const totalArea = components.reduce(
44411
+ (sum, component) => sum + (component.width + componentGap) * (component.height + componentGap),
44412
+ 0
44413
+ );
44414
+ const targetRowWidth = Math.max(960, Math.sqrt(totalArea) * 1.45);
44415
+ const positions = /* @__PURE__ */ new Map();
44416
+ const ranks = /* @__PURE__ */ new Map();
44417
+ let cursorX = outerPadding;
44418
+ let cursorY = outerPadding;
44419
+ let rowHeight = 0;
44420
+ for (const component of components) {
44421
+ if (cursorX > outerPadding && cursorX + component.width > targetRowWidth) {
44422
+ cursorX = outerPadding;
44423
+ cursorY += rowHeight + componentGap;
44424
+ rowHeight = 0;
44425
+ }
44426
+ for (const nodeId of component.nodeIds) {
44427
+ const point5 = component.positions.get(nodeId);
44428
+ positions.set(nodeId, { x: cursorX + point5.x, y: cursorY + point5.y });
44429
+ ranks.set(nodeId, component.ranks.get(nodeId) ?? 0);
44430
+ }
44431
+ cursorX += component.width + componentGap;
44432
+ rowHeight = Math.max(rowHeight, component.height);
44433
+ }
44434
+ const xValues = [...new Set([...positions.values()].map(({ x }) => x))].sort(
44435
+ (left, right) => left - right
44436
+ );
44437
+ const columnIndexByX = new Map(xValues.map((x, index2) => [x, index2]));
44438
+ const columns = xValues.map(
44439
+ (x) => [...positions].filter(([, point5]) => point5.x === x).sort(([, left], [, right]) => left.y - right.y).map(([nodeId]) => nodeId)
44440
+ );
44441
+ const columnIndexByNodeId = new Map(
44442
+ [...positions].map(([nodeId, point5]) => [nodeId, columnIndexByX.get(point5.x)])
44443
+ );
44444
+ const nonForwardDirectionalLinkCount = topology.directionalLinks.reduce(
44445
+ (count, [from, to]) => count + (columnIndexByNodeId.has(from) && columnIndexByNodeId.has(to) && columnIndexByNodeId.get(from) >= columnIndexByNodeId.get(to) ? 1 : 0),
44446
+ 0
44447
+ );
44448
+ const directionalLinkCount = topology.directionalLinks.length;
44449
+ const rowsPerColumn = columns.map((column) => column.length);
44450
+ const diagnostics = {
44451
+ columnCount: columns.length,
44452
+ rowsPerColumn,
44453
+ maxRows: Math.max(0, ...rowsPerColumn),
44454
+ directionalLinkCount,
44455
+ nonForwardDirectionalLinkCount,
44456
+ nonForwardDirectionalLinkRatio: directionalLinkCount === 0 ? 0 : nonForwardDirectionalLinkCount / directionalLinkCount,
44457
+ originOutgoingDirectionalLinkCount: topology.directionalLinks.filter(
44458
+ ([from]) => from === structure.originNodeId
44459
+ ).length
44460
+ };
44461
+ return {
44462
+ positionsByNodeId: positions,
44463
+ rankByNodeId: ranks,
44464
+ columnIndexByNodeId,
44465
+ columns,
44466
+ directionalLinks: topology.directionalLinks,
44467
+ diagnostics
44468
+ };
44469
+ }
44470
+ function structureAuthoringWarnings(diagnostics) {
44471
+ const warnings = [];
44472
+ if (diagnostics.originOutgoingDirectionalLinkCount === 0) {
44473
+ warnings.push({
44474
+ code: "STRUCTURE_ORIGIN_NO_OUTGOING_DIRECTIONAL_RELATION",
44475
+ message: "origin has no outgoing unambiguous directed relation; verify that it is the factual code entrypoint for this behavior. A terminal or intermediate origin may still be valid. Do not change factual relation direction solely to remove this warning."
44476
+ });
44477
+ }
44478
+ if (diagnostics.maxRows >= 8) {
44479
+ warnings.push({
44480
+ code: "STRUCTURE_LAYOUT_MAX_ROWS_HIGH",
44481
+ message: "the initial projection has a tall column; reconsider the factual origin, node granularity, overlapping or nested claims, whether multiple behaviors are mixed, and the subject boundary."
44482
+ });
44483
+ }
44484
+ if (diagnostics.nonForwardDirectionalLinkRatio >= 0.25) {
44485
+ warnings.push({
44486
+ code: "STRUCTURE_LAYOUT_NON_FORWARD_DIRECTIONAL_LINK_RATIO_HIGH",
44487
+ message: "many unambiguous directed relations are non-forward in the initial projection; reconsider the factual origin, node granularity, behavior boundary, and subject boundary. A factual graph may remain above this threshold. Do not change edge direction solely to improve the score."
44488
+ });
44489
+ }
44490
+ return warnings;
44491
+ }
44492
+
43808
44493
  // src/infrastructure/skills/skill-installer.ts
43809
44494
  import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
43810
44495
  import {
@@ -50080,6 +50765,10 @@ function writeJson(value) {
50080
50765
  process.stdout.write(`${JSON.stringify(value)}
50081
50766
  `);
50082
50767
  }
50768
+ function structureAuthoringFeedback(structure) {
50769
+ const layout = projectStructure(structure).diagnostics;
50770
+ return { layout, warnings: structureAuthoringWarnings(layout) };
50771
+ }
50083
50772
  function writeJsonSequence(value) {
50084
50773
  process.stdout.write(`${JSON.stringify(value)}
50085
50774
  `);
@@ -50687,6 +51376,7 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50687
51376
  "pullRequest.sync",
50688
51377
  "structure.list",
50689
51378
  "structure.read",
51379
+ "structure.preview",
50690
51380
  "structure.publish",
50691
51381
  "structure.update",
50692
51382
  "structure.delete",
@@ -50825,6 +51515,10 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50825
51515
  });
50826
51516
  const comment3 = program2.command("comment").description("\u4FDD\u5B58\u6E08\u307F\u30B3\u30E1\u30F3\u30C8\u3092\u64CD\u4F5C");
50827
51517
  const structure = program2.command("structure").description("source anchor\u4ED8\u304DStructure\u3092\u7BA1\u7406");
51518
+ structure.command("preview").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure content\u3092\u4FDD\u5B58\u305B\u305Acanonical initial layout\u3067\u691C\u67FB").action(async () => {
51519
+ const content3 = structureContentInputSchema.parse(await readStdinJson());
51520
+ writeJson({ ok: true, ...structureAuthoringFeedback(content3) });
51521
+ });
50828
51522
  structure.command("publish").requiredOption("--stdin", "stdin\u304B\u3089JSON\u3092\u8AAD\u3080").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u3092\u767B\u9332\uFF08viewer\u306F\u958B\u304B\u305Anavigation\u3082\u5909\u66F4\u3057\u306A\u3044\uFF09").action(async () => {
50829
51523
  const input = structurePublishInputSchema.parse(await readStdinJson());
50830
51524
  const published = await callService(
@@ -50832,7 +51526,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50832
51526
  input,
50833
51527
  async () => await getRuntime().service.publishStructure(input)
50834
51528
  );
50835
- writeJson({ ok: true, structure: published });
51529
+ const warnings = structureAuthoringFeedback(published).warnings;
51530
+ writeJson({ ok: true, structure: published, ...warnings.length > 0 ? { warnings } : {} });
50836
51531
  });
50837
51532
  structure.command("get").argument("<structure-uri>").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u306E\u73FE\u5728\u5185\u5BB9\u3092\u53D6\u5F97").action(async (uri) => {
50838
51533
  const result = await callService(
@@ -50857,7 +51552,8 @@ function createProgram(runtimeFactory = defaultRuntimeFactory) {
50857
51552
  { uri, content: content3 },
50858
51553
  async () => await getRuntime().service.updateStructure(uri, content3)
50859
51554
  );
50860
- writeJson({ ok: true, structure: updated });
51555
+ const warnings = structureAuthoringFeedback(updated).warnings;
51556
+ writeJson({ ok: true, structure: updated, ...warnings.length > 0 ? { warnings } : {} });
50861
51557
  });
50862
51558
  structure.command("delete").argument("<structure-uri>").option("--yes", "\u4E0D\u53EF\u9006\u306A\u524A\u9664\u3092\u78BA\u8A8D").option("--expected-updated-at <timestamp>", "preview\u3067\u53D6\u5F97\u3057\u305FupdatedAt").requiredOption("--json", "JSON\u3067\u51FA\u529B").description("Structure\u3092\u524A\u9664").action(
50863
51559
  async (uri, options) => {