@work-graph/cli 0.2.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/README.md +31 -0
- package/bin/work-graph.mjs +238 -0
- package/package.json +38 -0
- package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
- package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
- package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
- package/vendor/packages/workgraph-mcp/README.md +28 -0
- package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
- package/vendor/packages/workgraph-mcp/package.json +37 -0
- package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
- package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
- package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
- package/vendor/public/assets/workgraph-logo.svg +11 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
- package/vendor/public/graph-canvas-lit-flow.css +154 -0
- package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
- package/vendor/public/graph-canvas-lit-flow.js +8530 -0
- package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
- package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
- package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
- package/vendor/src/agentRunApi.mjs +136 -0
- package/vendor/src/agentToolLoopGuard.mjs +88 -0
- package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
- package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
- package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
- package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
- package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
- package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
- package/vendor/src/analyticsPanelProjection.mjs +212 -0
- package/vendor/src/analyticsRecordStore.mjs +165 -0
- package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
- package/vendor/src/architectureL1Canon.mjs +419 -0
- package/vendor/src/architectureLayout.mjs +229 -0
- package/vendor/src/architectureSnapshot.mjs +490 -0
- package/vendor/src/architectureViewsProjection.mjs +116 -0
- package/vendor/src/atomInspector.mjs +253 -0
- package/vendor/src/atomInspectorApi.mjs +130 -0
- package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
- package/vendor/src/backlogSchemaLint.mjs +176 -0
- package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
- package/vendor/src/bracketIrTraceSignal.mjs +93 -0
- package/vendor/src/bvcAtomParser.mjs +210 -0
- package/vendor/src/bvcDialectRegistry.mjs +86 -0
- package/vendor/src/bvcFileFormat.mjs +218 -0
- package/vendor/src/bvcFormatCli.mjs +55 -0
- package/vendor/src/bvcLintCli.mjs +48 -0
- package/vendor/src/bvcNewWritePolicy.mjs +70 -0
- package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
- package/vendor/src/claimNoEligibleEval.mjs +205 -0
- package/vendor/src/closingAnalysisSuggest.mjs +59 -0
- package/vendor/src/codeGapAnalyzer.mjs +308 -0
- package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
- package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
- package/vendor/src/codeGapOperatorProjection.mjs +60 -0
- package/vendor/src/codeSyntaxHighlight.mjs +123 -0
- package/vendor/src/codegenEvidence.mjs +187 -0
- package/vendor/src/compilerRoundTripCli.mjs +164 -0
- package/vendor/src/dagreGraphLayout.mjs +78 -0
- package/vendor/src/draftIntakePromotionRules.mjs +205 -0
- package/vendor/src/epicWorkScope.mjs +85 -0
- package/vendor/src/evalLiveLlmEnv.mjs +63 -0
- package/vendor/src/evidenceReadModel.mjs +167 -0
- package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
- package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
- package/vendor/src/goldenPath.mjs +69 -0
- package/vendor/src/graphCanvasLayout.mjs +464 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
- package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
- package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
- package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
- package/vendor/src/graphRagContextSlice.mjs +461 -0
- package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
- package/vendor/src/homeSnapshotApi.mjs +131 -0
- package/vendor/src/homeSnapshotProjection.mjs +275 -0
- package/vendor/src/inboxEventStream.mjs +140 -0
- package/vendor/src/intentComposerApi.mjs +245 -0
- package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
- package/vendor/src/intentGraphProjection.mjs +208 -0
- package/vendor/src/intentHierarchy.mjs +241 -0
- package/vendor/src/intentNodeLint.mjs +107 -0
- package/vendor/src/intentNodeRuntime.mjs +185 -0
- package/vendor/src/intentRoadmapCanvas.mjs +393 -0
- package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
- package/vendor/src/intentRoadmapMermaid.mjs +165 -0
- package/vendor/src/intentRoadmapProjection.mjs +85 -0
- package/vendor/src/intentTreeLint.mjs +114 -0
- package/vendor/src/intentTreeMigration.mjs +150 -0
- package/vendor/src/intentTreeWorkItems.mjs +227 -0
- package/vendor/src/kanbanBoardProjection.mjs +58 -0
- package/vendor/src/languageAdapterRegistry.mjs +180 -0
- package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
- package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
- package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
- package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
- package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
- package/vendor/src/languageAdapters/shared.mjs +68 -0
- package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
- package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
- package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
- package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
- package/vendor/src/markdownDocumentRender.mjs +208 -0
- package/vendor/src/memoryPanelProjection.mjs +116 -0
- package/vendor/src/memoryRecordWriter.mjs +243 -0
- package/vendor/src/memoryWorkerSlice.mjs +238 -0
- package/vendor/src/migrateStepToBvc.mjs +133 -0
- package/vendor/src/missionControlServerHandlers.mjs +195 -0
- package/vendor/src/missionControlUiClient.mjs +278 -0
- package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
- package/vendor/src/onebaseCliRunner.mjs +145 -0
- package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
- package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
- package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
- package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
- package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
- package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
- package/vendor/src/onebaseWorkerTools.mjs +586 -0
- package/vendor/src/operatorShellProjection.mjs +102 -0
- package/vendor/src/pipelineProseRender.mjs +180 -0
- package/vendor/src/pipelineStageLint.mjs +118 -0
- package/vendor/src/promptRulesEditorApi.mjs +174 -0
- package/vendor/src/promptRulesProjection.mjs +134 -0
- package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
- package/vendor/src/pvrgTaskScope.mjs +152 -0
- package/vendor/src/releaseGateMatrix.mjs +188 -0
- package/vendor/src/schematicView.mjs +305 -0
- package/vendor/src/seedAnalyticsRecord.mjs +217 -0
- package/vendor/src/semanticSearchBm25.mjs +103 -0
- package/vendor/src/semanticSearchExcerpts.mjs +68 -0
- package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
- package/vendor/src/semanticSearchWorkflow.mjs +366 -0
- package/vendor/src/stepAtomFormatter.mjs +413 -0
- package/vendor/src/stepGraphSlice.mjs +318 -0
- package/vendor/src/ui/atoms/badge.mjs +40 -0
- package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
- package/vendor/src/ui/atoms/button.mjs +114 -0
- package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
- package/vendor/src/ui/atoms/icon.mjs +23 -0
- package/vendor/src/ui/atoms/input.mjs +38 -0
- package/vendor/src/ui/atoms/modal.mjs +44 -0
- package/vendor/src/ui/atoms/select.mjs +98 -0
- package/vendor/src/ui/backlogShellButtons.mjs +238 -0
- package/vendor/src/ui/htmlEscape.mjs +11 -0
- package/vendor/src/ui/molecules/rating.mjs +48 -0
- package/vendor/src/ui/molecules/tabs.mjs +70 -0
- package/vendor/src/ui/organisms/modal.mjs +1 -0
- package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
- package/vendor/src/ui/workItemStatusTone.mjs +36 -0
- package/vendor/src/unifiedLinkageProjection.mjs +264 -0
- package/vendor/src/verificationLoop.mjs +206 -0
- package/vendor/src/workGraphBacklogPersist.mjs +234 -0
- package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
- package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
- package/vendor/src/workGraphCycleSlice.mjs +184 -0
- package/vendor/src/workGraphDaemonTick.mjs +307 -0
- package/vendor/src/workGraphDaemonWatch.mjs +157 -0
- package/vendor/src/workGraphEngineRoot.mjs +136 -0
- package/vendor/src/workGraphInstallLayout.mjs +65 -0
- package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
- package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
- package/vendor/src/workGraphProjectHost.mjs +149 -0
- package/vendor/src/workGraphProjectInit.mjs +392 -0
- package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
- package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
- package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
- package/vendor/src/workGraphRuntime.mjs +1008 -0
- package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
- package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
- package/vendor/src/workGraphWorkerProvider.mjs +600 -0
- package/vendor/src/workItemBvcQuality.mjs +262 -0
- package/vendor/src/workItemCreateAnalysis.mjs +157 -0
- package/vendor/src/workItemDecisionPipeline.mjs +278 -0
- package/vendor/src/workItemEpicCascade.mjs +176 -0
- package/vendor/src/workItemExecutionGate.mjs +78 -0
- package/vendor/src/workItemHierarchy.mjs +226 -0
- package/vendor/src/workItemProseLint.mjs +133 -0
- package/vendor/src/workItemTextRusify.mjs +794 -0
- package/vendor/src/workItemTraceEnvelope.mjs +158 -0
- package/vendor/src/workItemUiReferences.mjs +272 -0
- package/vendor/src/workflowEpicGrouping.mjs +67 -0
- package/vendor/src/workflowTreeProjection.mjs +53 -0
- package/vendor/src/workspaceRegistry.mjs +150 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { buildUnifiedLinkageProjectionV1 } from './unifiedLinkageProjection.mjs';
|
|
2
|
+
|
|
3
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
4
|
+
const EXPAND_RELATIONS = new Set(['targets', 'depends_on', 'references', 'verifies', 'edits']);
|
|
5
|
+
const DEFAULT_MAX_NODES = 24;
|
|
6
|
+
const DEFAULT_MAX_DEPTH = 2;
|
|
7
|
+
|
|
8
|
+
function nodeKey(node) {
|
|
9
|
+
return `${node.kind}:${node.id}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function workNode(item) {
|
|
13
|
+
return {
|
|
14
|
+
kind: 'work',
|
|
15
|
+
id: item.id,
|
|
16
|
+
title: item.title,
|
|
17
|
+
status: item.status,
|
|
18
|
+
targetFiles: [...(item.targetFiles ?? [])].sort(compareText),
|
|
19
|
+
traceStatus: item.traceStatus ?? '',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fileNode(path) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'file',
|
|
26
|
+
id: path,
|
|
27
|
+
path,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractFilePath(link) {
|
|
32
|
+
if (link.to.kind === 'file') {
|
|
33
|
+
return link.to.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ((link.to.kind === 'symbol' || link.to.kind === 'range') && link.to.locator?.path) {
|
|
37
|
+
return link.to.locator.path;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildPvrgTaskScopeSlice(items, taskId, options = {}) {
|
|
44
|
+
if (!Array.isArray(items)) {
|
|
45
|
+
throw new TypeError('items must be an array');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
49
|
+
const task = itemById.get(taskId);
|
|
50
|
+
if (task === undefined) {
|
|
51
|
+
throw new Error(`unknown task id: ${taskId}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const maxNodes = Number.isInteger(options.maxNodes) && options.maxNodes > 0 ? options.maxNodes : DEFAULT_MAX_NODES;
|
|
55
|
+
const maxDepth = Number.isInteger(options.maxDepth) && options.maxDepth >= 0 ? options.maxDepth : DEFAULT_MAX_DEPTH;
|
|
56
|
+
const linkage = options.linkage ?? buildUnifiedLinkageProjectionV1(items, options);
|
|
57
|
+
|
|
58
|
+
const nodes = new Map([[nodeKey(workNode(task)), workNode(task)]]);
|
|
59
|
+
const edges = [];
|
|
60
|
+
const queue = [{ workId: task.id, depth: 0 }];
|
|
61
|
+
const visitedWork = new Set([task.id]);
|
|
62
|
+
|
|
63
|
+
while (queue.length > 0 && nodes.size < maxNodes) {
|
|
64
|
+
const current = queue.shift();
|
|
65
|
+
const currentItem = itemById.get(current.workId);
|
|
66
|
+
if (currentItem === undefined) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const path of currentItem.targetFiles ?? []) {
|
|
71
|
+
const file = fileNode(path);
|
|
72
|
+
nodes.set(nodeKey(file), file);
|
|
73
|
+
edges.push({
|
|
74
|
+
from: { kind: 'work', id: current.workId },
|
|
75
|
+
to: { kind: 'file', id: path },
|
|
76
|
+
relation: 'targets',
|
|
77
|
+
source: 'work.target_files',
|
|
78
|
+
confidence: 'low',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const link of linkage.links) {
|
|
83
|
+
if (link.sourceWorkId !== current.workId && link.from.id !== current.workId) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!EXPAND_RELATIONS.has(link.relation)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
edges.push({
|
|
92
|
+
from: link.from,
|
|
93
|
+
to: link.to,
|
|
94
|
+
relation: link.relation,
|
|
95
|
+
source: link.sourceLabel ?? link.source,
|
|
96
|
+
confidence: link.confidence,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const filePath = extractFilePath(link);
|
|
100
|
+
if (filePath !== null && nodes.size < maxNodes) {
|
|
101
|
+
nodes.set(nodeKey(fileNode(filePath)), fileNode(filePath));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (link.to.kind === 'work' && current.depth < maxDepth && !visitedWork.has(link.to.id) && nodes.size < maxNodes) {
|
|
105
|
+
const related = itemById.get(link.to.id);
|
|
106
|
+
if (related !== undefined) {
|
|
107
|
+
visitedWork.add(link.to.id);
|
|
108
|
+
nodes.set(nodeKey(workNode(related)), workNode(related));
|
|
109
|
+
queue.push({ workId: link.to.id, depth: current.depth + 1 });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const dependencyId of currentItem.dependsOn ?? []) {
|
|
115
|
+
const dependency = itemById.get(dependencyId);
|
|
116
|
+
if (dependency === undefined) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
nodes.set(nodeKey(workNode(dependency)), workNode(dependency));
|
|
121
|
+
edges.push({
|
|
122
|
+
from: { kind: 'work', id: current.workId },
|
|
123
|
+
to: { kind: 'work', id: dependencyId },
|
|
124
|
+
relation: 'depends_on',
|
|
125
|
+
source: 'work.depends_on',
|
|
126
|
+
confidence: 'high',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (current.depth < maxDepth && !visitedWork.has(dependencyId) && nodes.size < maxNodes) {
|
|
130
|
+
visitedWork.add(dependencyId);
|
|
131
|
+
queue.push({ workId: dependencyId, depth: current.depth + 1 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sortedNodes = [...nodes.values()].sort((left, right) => compareText(nodeKey(left), nodeKey(right)));
|
|
137
|
+
const sortedEdges = edges.sort((left, right) =>
|
|
138
|
+
compareText(`${left.from.kind}:${left.from.id}\0${left.relation}\0${left.to.kind}:${left.to.id}`, `${right.from.kind}:${right.from.id}\0${right.relation}\0${right.to.kind}:${right.to.id}`),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
schema: 'pvrg.task-scope.slice.v1',
|
|
143
|
+
seedWorkId: taskId,
|
|
144
|
+
maxNodes,
|
|
145
|
+
maxDepth,
|
|
146
|
+
truncated: nodes.size >= maxNodes,
|
|
147
|
+
nodeCount: sortedNodes.length,
|
|
148
|
+
edgeCount: sortedEdges.length,
|
|
149
|
+
nodes: sortedNodes.slice(0, maxNodes),
|
|
150
|
+
edges: sortedEdges,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { VERIFICATION_MATRIX } from './verificationLoop.mjs';
|
|
2
|
+
|
|
3
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
4
|
+
|
|
5
|
+
export const RELEASE_GATE_TIERS = ['mandatory', 'mandatory-integration', 'optional-env', 'optional-llm'];
|
|
6
|
+
|
|
7
|
+
export const RELEASE_GATE_ROWS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'deterministic-test-suite',
|
|
10
|
+
tier: 'mandatory',
|
|
11
|
+
title: 'Work Graph deterministic test suite',
|
|
12
|
+
command: 'npm run test:deterministic',
|
|
13
|
+
registerPhase: 'all',
|
|
14
|
+
blocksRelease: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'backlog-schema-lint',
|
|
18
|
+
tier: 'mandatory',
|
|
19
|
+
title: 'Backlog schema lint',
|
|
20
|
+
command: 'npm run lint:backlog',
|
|
21
|
+
registerPhase: 'all',
|
|
22
|
+
blocksRelease: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'ci-mandatory-bundle',
|
|
26
|
+
tier: 'mandatory',
|
|
27
|
+
title: 'CI mandatory bundle (tests + lint)',
|
|
28
|
+
command: 'npm run ci:mandatory',
|
|
29
|
+
registerPhase: 'all',
|
|
30
|
+
blocksRelease: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'daemon-live-loop-integration',
|
|
34
|
+
tier: 'mandatory-integration',
|
|
35
|
+
title: 'Daemon tick + live-loop integration fixtures',
|
|
36
|
+
command: 'tests/workGraphDaemonIntegration.test.mjs',
|
|
37
|
+
registerPhase: 'phase-2+',
|
|
38
|
+
blocksRelease: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'intent-tree-parity',
|
|
42
|
+
tier: 'mandatory-integration',
|
|
43
|
+
title: 'Intent tree migration snapshot parity',
|
|
44
|
+
command: 'tests/intentTreeMigration.test.mjs (equivalence)',
|
|
45
|
+
registerPhase: 'phase-3+',
|
|
46
|
+
blocksRelease: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'compiler-roundtrip-fixture',
|
|
50
|
+
tier: 'mandatory-integration',
|
|
51
|
+
title: 'Compiler round-trip CLI on fixture step',
|
|
52
|
+
command: 'npm run compiler:roundtrip -- tests/fixtures/compiler-roundtrip/sample.compiler.bvc',
|
|
53
|
+
registerPhase: 'phase-5+',
|
|
54
|
+
blocksRelease: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'code-gap-analyzer-fixture',
|
|
58
|
+
tier: 'mandatory-integration',
|
|
59
|
+
title: 'Code-gap analyzer CLI on fixture repo',
|
|
60
|
+
command: 'node --test tests/codeGapAnalyzer.test.mjs',
|
|
61
|
+
registerPhase: 'phase-5+',
|
|
62
|
+
blocksRelease: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'onebase-go-optional',
|
|
66
|
+
tier: 'optional-env',
|
|
67
|
+
title: 'OneBase go test (environment)',
|
|
68
|
+
command: 'npm run test:optional:onebase',
|
|
69
|
+
registerPhase: 'phase-7+',
|
|
70
|
+
blocksRelease: false,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'onebase-check-optional',
|
|
74
|
+
tier: 'optional-env',
|
|
75
|
+
title: 'OneBase config check (CLI)',
|
|
76
|
+
command: 'npm run test:optional:onebase-check',
|
|
77
|
+
registerPhase: 'phase-7+',
|
|
78
|
+
blocksRelease: false,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'optional-blocked-onebase-go-preflight',
|
|
82
|
+
tier: 'optional-env',
|
|
83
|
+
title: 'OneBase go preflight blocked evidence fixture',
|
|
84
|
+
command: 'npm run eval:optional:blocked-onebase-go',
|
|
85
|
+
registerPhase: 'phase-7+',
|
|
86
|
+
blocksRelease: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'lowcode-verify-optional',
|
|
90
|
+
tier: 'optional-env',
|
|
91
|
+
title: 'Low-code arch-rules scaffold verify',
|
|
92
|
+
command: 'npm run verify:lowcode',
|
|
93
|
+
registerPhase: 'audit-gap+',
|
|
94
|
+
blocksRelease: false,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'golden-path-llm-optional',
|
|
98
|
+
tier: 'optional-llm',
|
|
99
|
+
title: 'Golden path LLM eval',
|
|
100
|
+
command: 'npm run test:optional:golden-path-llm',
|
|
101
|
+
registerPhase: 'phase-8+',
|
|
102
|
+
blocksRelease: false,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'optional-llm-claim-no-eligible',
|
|
106
|
+
tier: 'optional-llm',
|
|
107
|
+
title: 'Optional LLM: claim-no-eligible fixture',
|
|
108
|
+
command: 'npm run eval:optional:claim-no-eligible',
|
|
109
|
+
registerPhase: 'phase-8+',
|
|
110
|
+
blocksRelease: false,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'optional-llm-loop-hint',
|
|
114
|
+
tier: 'optional-llm',
|
|
115
|
+
title: 'Optional LLM: loop-hint repeat-tool fixture',
|
|
116
|
+
command: 'npm run eval:optional:loop-hint',
|
|
117
|
+
registerPhase: 'phase-8+',
|
|
118
|
+
blocksRelease: false,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'optional-llm-live-eval',
|
|
122
|
+
tier: 'optional-llm',
|
|
123
|
+
title: 'Live LLM eval (golden path rubric)',
|
|
124
|
+
command: 'npm run eval:live-llm',
|
|
125
|
+
registerPhase: 'phase-8+',
|
|
126
|
+
blocksRelease: false,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'operator-dashboard-e2e',
|
|
130
|
+
tier: 'optional-env',
|
|
131
|
+
title: 'Playwright operator dashboard smoke',
|
|
132
|
+
command: 'npm run test:e2e',
|
|
133
|
+
registerPhase: 'phase-10+',
|
|
134
|
+
blocksRelease: false,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'optional-gvm-verify',
|
|
138
|
+
tier: 'optional-env',
|
|
139
|
+
title: 'Optional GVM verify worker preflight stub',
|
|
140
|
+
command: 'npm run eval:optional:gvm-verify',
|
|
141
|
+
registerPhase: 'phase-11+',
|
|
142
|
+
blocksRelease: false,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'gbc-module-slice-pilot',
|
|
146
|
+
tier: 'optional-env',
|
|
147
|
+
title: 'GBC module slice pilot probe (read-only)',
|
|
148
|
+
command: 'npm run probe:gbc-module-slice-pilot',
|
|
149
|
+
registerPhase: 'phase-11+',
|
|
150
|
+
blocksRelease: false,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export function buildReleaseGateMatrix() {
|
|
155
|
+
const rows = [...RELEASE_GATE_ROWS].sort((left, right) => compareText(left.id, right.id));
|
|
156
|
+
const byTier = Object.fromEntries(
|
|
157
|
+
RELEASE_GATE_TIERS.map((tier) => [tier, rows.filter((row) => row.tier === tier)]),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
schema: 'workgraph.release.gate.matrix.v1',
|
|
162
|
+
tiers: [...RELEASE_GATE_TIERS],
|
|
163
|
+
rows,
|
|
164
|
+
byTier,
|
|
165
|
+
verificationMatrix: VERIFICATION_MATRIX,
|
|
166
|
+
policy: {
|
|
167
|
+
mandatoryCommand: 'npm run ci:mandatory',
|
|
168
|
+
deterministicCommand: 'npm run test:deterministic',
|
|
169
|
+
backlogLintCommand: 'npm run lint:backlog',
|
|
170
|
+
optionalEnvCommand: 'npm run test:optional:onebase',
|
|
171
|
+
optionalBlockedOnebaseGoCommand: 'npm run eval:optional:blocked-onebase-go',
|
|
172
|
+
optionalBlockedOnebaseGoEnv: 'Deterministic stub; no Go toolchain or live LLM required',
|
|
173
|
+
optionalLlmCommands: [
|
|
174
|
+
'npm run test:optional:golden-path-llm',
|
|
175
|
+
'npm run eval:optional:claim-no-eligible',
|
|
176
|
+
'npm run eval:optional:loop-hint',
|
|
177
|
+
'npm run eval:live-llm',
|
|
178
|
+
],
|
|
179
|
+
optionalLlmCommand: 'npm run test:optional:golden-path-llm',
|
|
180
|
+
optionalE2eCommand: 'npm run test:e2e',
|
|
181
|
+
optionalE2eEnv: 'IOHASC_E2E_REAL_LLM not required; Playwright uses isolated fixture via WORKGRAPH_E2E_ROOT',
|
|
182
|
+
optionalGvmVerifyCommand: 'npm run eval:optional:gvm-verify',
|
|
183
|
+
optionalGvmVerifyEnv: 'IOHASC_GVM_VERIFY=1 enables stub preflight; skips when .iohasc/cache wasm slices missing',
|
|
184
|
+
optionalGbcPilotCommand: 'npm run probe:gbc-module-slice-pilot',
|
|
185
|
+
modelQualityNeverBlocksMandatory: true,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GRAPH_CANVAS_VIEW_FULL,
|
|
3
|
+
GRAPH_CANVAS_VIEW_PIPELINE,
|
|
4
|
+
SCHEMATIC_LAYOUT_PROFILE,
|
|
5
|
+
SCHEMATIC_PIPELINE_LAYOUT_PROFILE,
|
|
6
|
+
filterGraphForViewMode,
|
|
7
|
+
placeGraphCanvasNodes,
|
|
8
|
+
resolveLayoutProfileForViewMode,
|
|
9
|
+
} from './graphCanvasLayout.mjs';
|
|
10
|
+
|
|
11
|
+
export { GRAPH_CANVAS_VIEW_FULL, GRAPH_CANVAS_VIEW_PIPELINE };
|
|
12
|
+
|
|
13
|
+
const NODE_WIDTH = 232;
|
|
14
|
+
const NODE_MIN_HEIGHT = 112;
|
|
15
|
+
const COL_GAP = 72;
|
|
16
|
+
const ROW_GAP = 96;
|
|
17
|
+
const OFFSET_X = 40;
|
|
18
|
+
const OFFSET_Y = 40;
|
|
19
|
+
const NODE_PADDING_X = 28;
|
|
20
|
+
const HEADER_BLOCK_HEIGHT = 54;
|
|
21
|
+
const SUMMARY_LINE_HEIGHT = 22;
|
|
22
|
+
const SUMMARY_CHARS_PER_LINE = 26;
|
|
23
|
+
|
|
24
|
+
const GRID_CONFIG = {
|
|
25
|
+
nodeWidth: NODE_WIDTH,
|
|
26
|
+
nodeMinHeight: NODE_MIN_HEIGHT,
|
|
27
|
+
colGap: COL_GAP,
|
|
28
|
+
rowGap: ROW_GAP,
|
|
29
|
+
offsetX: OFFSET_X,
|
|
30
|
+
offsetY: OFFSET_Y,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** @typedef {{ id: string, title: string, summary: string, layer: string, action?: string, target?: string, protocolPath?: string }} SchematicNodeDef */
|
|
34
|
+
/** @typedef {{ id: string, from: string, to: string, type: string, label: string, upstream?: boolean }} SchematicEdgeDef */
|
|
35
|
+
|
|
36
|
+
/** @type {SchematicNodeDef[]} */
|
|
37
|
+
export const SCHEMATIC_NODE_DEFS = [
|
|
38
|
+
{
|
|
39
|
+
id: 'intent-tree',
|
|
40
|
+
title: 'Дерево intent',
|
|
41
|
+
summary: 'Каноничная раскладка .bvc атомов: корни intent, протоколы и задачи как устойчивая поверхность авторинга.',
|
|
42
|
+
layer: 'авторинг',
|
|
43
|
+
protocolPath: 'intent/index.bvc',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'work-graph',
|
|
47
|
+
title: 'Work Graph',
|
|
48
|
+
summary: 'Источник правды runtime: статусы, depends_on, target files, blockers и transition gates.',
|
|
49
|
+
layer: 'рантайм',
|
|
50
|
+
action: 'view:board',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'runner',
|
|
54
|
+
title: 'Runner задач',
|
|
55
|
+
summary: 'Производная очередь и локальный запуск worker поверх runnable WorkItem projections.',
|
|
56
|
+
layer: 'исполнение',
|
|
57
|
+
protocolPath: 'protocols/agent-worker-adapter.bvc',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'evidence',
|
|
61
|
+
title: 'Доказательства',
|
|
62
|
+
summary: 'Записи проверок, worker runs, blockers и gate outcomes, связанные с WorkItems.',
|
|
63
|
+
layer: 'проверка',
|
|
64
|
+
protocolPath: 'protocols/evidence-model-v1.bvc',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'memory',
|
|
68
|
+
title: 'Память проекта',
|
|
69
|
+
summary: 'Устойчивые решения, инварианты и факты, извлечённые из done work и operator review.',
|
|
70
|
+
layer: 'память',
|
|
71
|
+
protocolPath: 'protocols/project-memory-v1.bvc',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'graph-rag',
|
|
75
|
+
title: 'Graph / RAG',
|
|
76
|
+
summary: 'Производный context slice поверх Work Graph, доказательств, памяти и adapter facts для retrieval.',
|
|
77
|
+
layer: 'проекция',
|
|
78
|
+
action: 'view:board',
|
|
79
|
+
protocolPath: 'protocols/pvrg-graph-rag-minimal.bvc',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'ui',
|
|
83
|
+
title: 'UI оператора',
|
|
84
|
+
summary: 'Доска, backlog, architecture map, schematic view и task detail drawer для operator loop.',
|
|
85
|
+
layer: 'проекция',
|
|
86
|
+
action: 'view:board',
|
|
87
|
+
protocolPath: 'ui/operator-dashboard.bvc',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'domains',
|
|
91
|
+
title: 'Домены',
|
|
92
|
+
summary: 'L1 hub прикладных verticals: OneBase и Marketplace (L2) с maps_to на Work Graph.',
|
|
93
|
+
layer: 'домен',
|
|
94
|
+
protocolPath: 'intent/domains',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'storage',
|
|
98
|
+
title: 'Хранилище / Snapshot',
|
|
99
|
+
summary: '.bvc canon и deterministic JSON exports, пересобираемые для dashboard, worker и tests.',
|
|
100
|
+
layer: 'хранилище',
|
|
101
|
+
protocolPath: 'work/backlog.bvc',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/** @type {SchematicEdgeDef[]} */
|
|
106
|
+
export const SCHEMATIC_EDGE_DEFS = [
|
|
107
|
+
{ id: 'intent-work', from: 'intent-tree', to: 'work-graph', type: 'feeds', label: 'питает' },
|
|
108
|
+
{ id: 'work-runner', from: 'work-graph', to: 'runner', type: 'uses', label: 'использует' },
|
|
109
|
+
{ id: 'runner-evidence', from: 'runner', to: 'evidence', type: 'feeds', label: 'пишет' },
|
|
110
|
+
{ id: 'evidence-work', from: 'evidence', to: 'work-graph', type: 'validates', label: 'проверяет', upstream: true },
|
|
111
|
+
{ id: 'evidence-memory', from: 'evidence', to: 'memory', type: 'feeds', label: 'пополняет' },
|
|
112
|
+
{ id: 'memory-graph', from: 'memory', to: 'graph-rag', type: 'feeds', label: 'даёт контекст' },
|
|
113
|
+
{ id: 'graph-ui', from: 'graph-rag', to: 'ui', type: 'feeds', label: 'показывает' },
|
|
114
|
+
{ id: 'work-ui', from: 'work-graph', to: 'ui', type: 'feeds', label: 'показывает' },
|
|
115
|
+
{ id: 'storage-work', from: 'storage', to: 'work-graph', type: 'rebuilds', label: 'пересобирает', upstream: true },
|
|
116
|
+
{ id: 'storage-evidence', from: 'storage', to: 'evidence', type: 'rebuilds', label: 'пересобирает', upstream: true },
|
|
117
|
+
{ id: 'storage-memory', from: 'storage', to: 'memory', type: 'rebuilds', label: 'пересобирает', upstream: true },
|
|
118
|
+
{ id: 'storage-graph', from: 'storage', to: 'graph-rag', type: 'rebuilds', label: 'пересобирает', upstream: true },
|
|
119
|
+
{ id: 'domains-work', from: 'domains', to: 'work-graph', type: 'maps_to', label: 'сопоставляется', upstream: true },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
function estimateSummaryLines(summary, width) {
|
|
123
|
+
const charsPerLine = Math.max(
|
|
124
|
+
18,
|
|
125
|
+
Math.floor((width - NODE_PADDING_X) / (NODE_WIDTH / SUMMARY_CHARS_PER_LINE)),
|
|
126
|
+
);
|
|
127
|
+
return Math.max(1, Math.ceil(String(summary).length / charsPerLine));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function estimateNodeHeight(summary, width) {
|
|
131
|
+
const lines = estimateSummaryLines(summary, width);
|
|
132
|
+
return Math.max(
|
|
133
|
+
NODE_MIN_HEIGHT,
|
|
134
|
+
HEADER_BLOCK_HEIGHT + lines * SUMMARY_LINE_HEIGHT + 20,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {object} [options]
|
|
140
|
+
* @returns {{
|
|
141
|
+
* schema: 'schematic.view.v1',
|
|
142
|
+
* nodes: Array<SchematicNodeDef & { x: number, y: number, width: number, height: number }>,
|
|
143
|
+
* edges: Array<SchematicEdgeDef & { fromNode: object, toNode: object }>,
|
|
144
|
+
* width: number,
|
|
145
|
+
* height: number,
|
|
146
|
+
* viewMode: string,
|
|
147
|
+
* layoutProfile: object,
|
|
148
|
+
* }}
|
|
149
|
+
*/
|
|
150
|
+
export function buildSchematicViewModel(options = {}) {
|
|
151
|
+
const viewMode = options.viewMode ?? GRAPH_CANVAS_VIEW_FULL;
|
|
152
|
+
const layoutProfile = options.layoutProfile
|
|
153
|
+
?? resolveLayoutProfileForViewMode(viewMode, {
|
|
154
|
+
full: SCHEMATIC_LAYOUT_PROFILE,
|
|
155
|
+
pipeline: SCHEMATIC_PIPELINE_LAYOUT_PROFILE,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const filtered = filterGraphForViewMode({
|
|
159
|
+
nodes: SCHEMATIC_NODE_DEFS,
|
|
160
|
+
nodeIds: SCHEMATIC_NODE_DEFS.map((node) => node.id),
|
|
161
|
+
edges: SCHEMATIC_EDGE_DEFS,
|
|
162
|
+
viewMode,
|
|
163
|
+
pipelineNodeIds: layoutProfile.pipelineNodeIds ?? [],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const placed = placeGraphCanvasNodes({
|
|
167
|
+
items: filtered.nodes,
|
|
168
|
+
edges: filtered.edges,
|
|
169
|
+
layoutProfile,
|
|
170
|
+
estimateSize: (node, slot) => {
|
|
171
|
+
const colSpan = slot.colSpan ?? 1;
|
|
172
|
+
const width = NODE_WIDTH * colSpan + COL_GAP * Math.max(0, colSpan - 1);
|
|
173
|
+
return {
|
|
174
|
+
width,
|
|
175
|
+
height: estimateNodeHeight(node.summary, width),
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
config: GRID_CONFIG,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const nodes = placed.map((node) => ({
|
|
182
|
+
...node.item,
|
|
183
|
+
x: node.x,
|
|
184
|
+
y: node.y,
|
|
185
|
+
width: node.width,
|
|
186
|
+
height: node.height,
|
|
187
|
+
col: node.col,
|
|
188
|
+
row: node.row,
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
192
|
+
const edges = filtered.edges.map((edge) => ({
|
|
193
|
+
...edge,
|
|
194
|
+
fromNode: nodeById.get(edge.from),
|
|
195
|
+
toNode: nodeById.get(edge.to),
|
|
196
|
+
}))
|
|
197
|
+
.filter((edge) => edge.fromNode && edge.toNode)
|
|
198
|
+
.map((edge) => ({
|
|
199
|
+
...edge,
|
|
200
|
+
geometry: schematicEdgeGeometry(edge),
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
const maxX = Math.max(...nodes.map((node) => node.x + node.width));
|
|
204
|
+
const maxY = Math.max(...nodes.map((node) => node.y + node.height));
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
schema: 'schematic.view.v1',
|
|
208
|
+
nodes,
|
|
209
|
+
edges,
|
|
210
|
+
width: maxX + OFFSET_X,
|
|
211
|
+
height: maxY + OFFSET_Y,
|
|
212
|
+
viewMode,
|
|
213
|
+
layoutProfile,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function anchorX(node, towardX) {
|
|
218
|
+
const ratio = (towardX - node.x) / node.width;
|
|
219
|
+
return node.x + node.width * Math.max(0.12, Math.min(0.88, ratio));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {ReturnType<typeof buildSchematicViewModel>['edges'][number]} edge
|
|
224
|
+
*/
|
|
225
|
+
export function schematicEdgeGeometry(edge) {
|
|
226
|
+
const from = edge.fromNode;
|
|
227
|
+
const to = edge.toNode;
|
|
228
|
+
const fromCx = from.x + from.width / 2;
|
|
229
|
+
const fromCy = from.y + from.height / 2;
|
|
230
|
+
const toCx = to.x + to.width / 2;
|
|
231
|
+
const toCy = to.y + to.height / 2;
|
|
232
|
+
const dx = toCx - fromCx;
|
|
233
|
+
const dy = toCy - fromCy;
|
|
234
|
+
|
|
235
|
+
let startX;
|
|
236
|
+
let startY;
|
|
237
|
+
let endX;
|
|
238
|
+
let endY;
|
|
239
|
+
let orientation;
|
|
240
|
+
|
|
241
|
+
if (from.row !== to.row) {
|
|
242
|
+
orientation = 'vertical';
|
|
243
|
+
const startBiasX = anchorX(from, toCx);
|
|
244
|
+
const endBiasX = anchorX(to, fromCx);
|
|
245
|
+
if (to.row > from.row) {
|
|
246
|
+
startX = startBiasX;
|
|
247
|
+
startY = from.y + from.height;
|
|
248
|
+
endX = endBiasX;
|
|
249
|
+
endY = to.y;
|
|
250
|
+
} else {
|
|
251
|
+
startX = startBiasX;
|
|
252
|
+
startY = from.y;
|
|
253
|
+
endX = endBiasX;
|
|
254
|
+
endY = to.y + to.height;
|
|
255
|
+
}
|
|
256
|
+
} else if (Math.abs(dx) >= Math.abs(dy) * 0.55) {
|
|
257
|
+
orientation = 'horizontal';
|
|
258
|
+
if (dx > 0) {
|
|
259
|
+
startX = from.x + from.width;
|
|
260
|
+
startY = fromCy;
|
|
261
|
+
endX = to.x;
|
|
262
|
+
endY = toCy;
|
|
263
|
+
} else {
|
|
264
|
+
startX = from.x;
|
|
265
|
+
startY = fromCy;
|
|
266
|
+
endX = to.x + to.width;
|
|
267
|
+
endY = toCy;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
orientation = 'vertical';
|
|
271
|
+
const startBiasX = anchorX(from, toCx);
|
|
272
|
+
const endBiasX = anchorX(to, fromCx);
|
|
273
|
+
if (dy >= 0) {
|
|
274
|
+
startX = startBiasX;
|
|
275
|
+
startY = from.y + from.height;
|
|
276
|
+
endX = endBiasX;
|
|
277
|
+
endY = to.y;
|
|
278
|
+
} else {
|
|
279
|
+
startX = startBiasX;
|
|
280
|
+
startY = from.y;
|
|
281
|
+
endX = endBiasX;
|
|
282
|
+
endY = to.y + to.height;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const bend = Math.max(32, Math.min(88, (Math.abs(dx) + Math.abs(dy)) * 0.22));
|
|
287
|
+
const d = orientation === 'vertical'
|
|
288
|
+
? `M ${startX} ${startY} C ${startX} ${startY + Math.sign(dy || 1) * bend}, ${endX} ${endY - Math.sign(dy || 1) * bend}, ${endX} ${endY}`
|
|
289
|
+
: (() => {
|
|
290
|
+
const midX = startX + (endX - startX) / 2;
|
|
291
|
+
return `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`;
|
|
292
|
+
})();
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
d,
|
|
296
|
+
startX,
|
|
297
|
+
startY,
|
|
298
|
+
endX,
|
|
299
|
+
endY,
|
|
300
|
+
orientation,
|
|
301
|
+
upstream: Boolean(edge.upstream),
|
|
302
|
+
labelX: (startX + endX) / 2,
|
|
303
|
+
labelY: orientation === 'horizontal' ? startY - 10 : startY + (endY - startY) / 2,
|
|
304
|
+
};
|
|
305
|
+
}
|