@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,60 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { buildCodeGapBacklogFeed } from './codeGapBacklogFeeder.mjs';
|
|
5
|
+
import { buildCodeGapBacklogCandidateList } from './codeGapDraftIntakeApi.mjs';
|
|
6
|
+
|
|
7
|
+
export { analyzeCodeGaps, CODE_GAP_REPORT_SCHEMA } from './codeGapAnalyzer.mjs';
|
|
8
|
+
|
|
9
|
+
export const CODE_GAP_PROJECTION_SCHEMA = 'workgraph.code-gap-projection.v1';
|
|
10
|
+
export const DEFAULT_CODE_GAP_REPORT_PATH = 'tests/fixtures/code-gap-report.v1.json';
|
|
11
|
+
|
|
12
|
+
export async function readCodeGapReport(options = {}) {
|
|
13
|
+
const cwd = options.cwd ?? process.cwd();
|
|
14
|
+
const reportPath = resolve(cwd, options.reportPath ?? DEFAULT_CODE_GAP_REPORT_PATH);
|
|
15
|
+
|
|
16
|
+
if (options.report !== undefined) {
|
|
17
|
+
return options.report;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const text = await readFile(reportPath, 'utf8');
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error && typeof error === 'object' && error.code === 'ENOENT') {
|
|
25
|
+
return {
|
|
26
|
+
schema: 'code-gap.report.v1',
|
|
27
|
+
summary: { total: 0 },
|
|
28
|
+
entries: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function buildCodeGapOperatorProjection(options = {}) {
|
|
37
|
+
const cwd = options.cwd ?? process.cwd();
|
|
38
|
+
const reportPath = options.reportPath ?? DEFAULT_CODE_GAP_REPORT_PATH;
|
|
39
|
+
const report = await readCodeGapReport({ cwd, reportPath, report: options.report });
|
|
40
|
+
const feed = buildCodeGapBacklogFeed(report, options);
|
|
41
|
+
const promotionCandidates = buildCodeGapBacklogCandidateList(
|
|
42
|
+
{
|
|
43
|
+
suggestions: feed.suggestions,
|
|
44
|
+
sourceReportPath: reportPath,
|
|
45
|
+
},
|
|
46
|
+
options,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
schema: CODE_GAP_PROJECTION_SCHEMA,
|
|
51
|
+
reportSchema: report.schema ?? 'code-gap.report.v1',
|
|
52
|
+
sourceReportPath: reportPath,
|
|
53
|
+
reportSummary: feed.sourceReport ?? {},
|
|
54
|
+
suggestionCount: feed.suggestionCount,
|
|
55
|
+
reviewRequired: feed.reviewRequired,
|
|
56
|
+
promotionProtocol: feed.promotionProtocol,
|
|
57
|
+
suggestions: feed.suggestions,
|
|
58
|
+
promotionCandidates,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
function escapeHtml(value) {
|
|
2
|
+
return String(value ?? '')
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const JS_KEYWORDS = new Set([
|
|
10
|
+
'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default', 'delete',
|
|
11
|
+
'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'from', 'function', 'if', 'import',
|
|
12
|
+
'in', 'let', 'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
|
|
13
|
+
'undefined', 'var', 'void', 'while',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export function normalizeCodeLanguage(language) {
|
|
17
|
+
const value = String(language ?? '').trim().toLowerCase();
|
|
18
|
+
if (value === 'yml') return 'yaml';
|
|
19
|
+
if (value === 'js') return 'javascript';
|
|
20
|
+
if (value === 'ts') return 'typescript';
|
|
21
|
+
if (value === 'sh' || value === 'shell') return 'bash';
|
|
22
|
+
return value || 'plaintext';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function highlightStructuredTokens(text) {
|
|
26
|
+
let result = '';
|
|
27
|
+
let index = 0;
|
|
28
|
+
const source = String(text ?? '');
|
|
29
|
+
|
|
30
|
+
while (index < source.length) {
|
|
31
|
+
const char = source[index];
|
|
32
|
+
|
|
33
|
+
if (char === '"' || char === '\'' || char === '`') {
|
|
34
|
+
const quote = char;
|
|
35
|
+
let end = index + 1;
|
|
36
|
+
while (end < source.length && source[end] !== quote) {
|
|
37
|
+
end += 1;
|
|
38
|
+
}
|
|
39
|
+
if (end < source.length) {
|
|
40
|
+
end += 1;
|
|
41
|
+
}
|
|
42
|
+
result += `<span class="code-hl-string">${escapeHtml(source.slice(index, end))}</span>`;
|
|
43
|
+
index = end;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (/[{}[\],():]/u.test(char)) {
|
|
48
|
+
result += `<span class="code-hl-punct">${escapeHtml(char)}</span>`;
|
|
49
|
+
index += 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const numberMatch = source.slice(index).match(/^\d+(?:\.\d+)?/u);
|
|
54
|
+
if (numberMatch && (index === 0 || /[\s,:[{(-]/u.test(source[index - 1]))) {
|
|
55
|
+
result += `<span class="code-hl-number">${escapeHtml(numberMatch[0])}</span>`;
|
|
56
|
+
index += numberMatch[0].length;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wordMatch = source.slice(index).match(/^[\w.-]+/u);
|
|
61
|
+
if (wordMatch) {
|
|
62
|
+
const word = wordMatch[0];
|
|
63
|
+
const className = JS_KEYWORDS.has(word) ? 'code-hl-keyword' : 'code-hl-key';
|
|
64
|
+
result += `<span class="${className}">${escapeHtml(word)}</span>`;
|
|
65
|
+
index += word.length;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
result += escapeHtml(char);
|
|
70
|
+
index += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function highlightYaml(source) {
|
|
77
|
+
return String(source ?? '').split('\n').map((line) => {
|
|
78
|
+
if (/^\s*#/u.test(line)) {
|
|
79
|
+
return `<span class="code-hl-comment">${escapeHtml(line)}</span>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const keyMatch = line.match(/^(\s*)([\w.-]+)(\s*:\s*)([\s\S]*)$/u);
|
|
83
|
+
if (keyMatch) {
|
|
84
|
+
const [, indent, key, separator, rest] = keyMatch;
|
|
85
|
+
const restTrimmed = rest.trim();
|
|
86
|
+
if (restTrimmed !== '' && !/[{}[\]]/u.test(restTrimmed)) {
|
|
87
|
+
return `${escapeHtml(indent)}<span class="code-hl-key">${escapeHtml(key)}</span>${escapeHtml(separator)}<span class="code-hl-string">${escapeHtml(rest)}</span>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `${escapeHtml(indent)}<span class="code-hl-key">${escapeHtml(key)}</span>${escapeHtml(separator)}${highlightStructuredTokens(rest)}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return highlightStructuredTokens(line);
|
|
94
|
+
}).join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function highlightJavaScript(source) {
|
|
98
|
+
return String(source ?? '').split('\n').map((line) => {
|
|
99
|
+
const commentMatch = line.match(/^(\s*)(\/\/.*)$/u);
|
|
100
|
+
if (commentMatch) {
|
|
101
|
+
return `${escapeHtml(commentMatch[1])}<span class="code-hl-comment">${escapeHtml(commentMatch[2])}</span>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return highlightStructuredTokens(line);
|
|
105
|
+
}).join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function highlightCodeBlock(source, language) {
|
|
109
|
+
const normalized = normalizeCodeLanguage(language);
|
|
110
|
+
|
|
111
|
+
switch (normalized) {
|
|
112
|
+
case 'yaml':
|
|
113
|
+
return highlightYaml(source);
|
|
114
|
+
case 'json':
|
|
115
|
+
return highlightStructuredTokens(source);
|
|
116
|
+
case 'javascript':
|
|
117
|
+
case 'typescript':
|
|
118
|
+
case 'bash':
|
|
119
|
+
return highlightJavaScript(source);
|
|
120
|
+
default:
|
|
121
|
+
return escapeHtml(source);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { evaluateBracketIrDrift } from './bracketIrTraceSignal.mjs';
|
|
4
|
+
|
|
5
|
+
const CODEGEN_KINDS = new Set(['integrity', 'roundtrip', 'generated_test']);
|
|
6
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
7
|
+
|
|
8
|
+
function createStableId(value) {
|
|
9
|
+
return createHash('sha256').update(String(value), 'utf8').digest('hex').slice(0, 16);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isCodegenFacingWorkItem(item) {
|
|
13
|
+
const labels = item.labels ?? {};
|
|
14
|
+
return Boolean(
|
|
15
|
+
labels['trace.codegen_source_step']
|
|
16
|
+
|| labels['trace.codegen_integrity_hash']
|
|
17
|
+
|| labels['compiler.mode']
|
|
18
|
+
|| labels['trace.bracket_ir_hash'],
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildCodegenEvidenceRecord({ kind, taskId, summary, status = 'succeeded', artifacts = [], command = '', exitCode = 0, details = {} }) {
|
|
23
|
+
if (!CODEGEN_KINDS.has(kind)) {
|
|
24
|
+
throw new Error(`unsupported codegen evidence kind: ${kind}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const idSuffix = [taskId, kind, summary, ...artifacts].join('\0');
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
schema: 'codegen.evidence.record.v1',
|
|
31
|
+
id: `codegen:${kind}:${createStableId(idSuffix)}`,
|
|
32
|
+
kind,
|
|
33
|
+
taskId,
|
|
34
|
+
summary,
|
|
35
|
+
status,
|
|
36
|
+
artifacts: [...artifacts].sort(compareText),
|
|
37
|
+
command,
|
|
38
|
+
exitCode,
|
|
39
|
+
details: { ...details, codegenKind: kind },
|
|
40
|
+
evidenceV1: {
|
|
41
|
+
type: kind === 'generated_test' || command ? 'test' : 'command',
|
|
42
|
+
summary,
|
|
43
|
+
status: status === 'succeeded' ? 'succeeded' : 'failed',
|
|
44
|
+
artifacts,
|
|
45
|
+
command: command || undefined,
|
|
46
|
+
exitCode: command ? exitCode : undefined,
|
|
47
|
+
details: { codegenKind: kind, ...details },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildCodegenRoundtripEvidence(result) {
|
|
53
|
+
const status = result.status === 'passed' ? 'succeeded' : result.status === 'skipped' ? 'skipped' : 'failed';
|
|
54
|
+
return buildCodegenEvidenceRecord({
|
|
55
|
+
kind: 'roundtrip',
|
|
56
|
+
taskId: result.taskId ?? 'unknown',
|
|
57
|
+
summary: result.diffSummary ?? `Compiler round-trip ${result.status} for ${result.stepPath}`,
|
|
58
|
+
status,
|
|
59
|
+
artifacts: [result.stepPath, ...(result.generatedPaths ?? [])].filter(Boolean),
|
|
60
|
+
command: result.command ?? '',
|
|
61
|
+
exitCode: result.exitCode ?? (status === 'succeeded' ? 0 : 1),
|
|
62
|
+
details: {
|
|
63
|
+
stepPath: result.stepPath,
|
|
64
|
+
engineVersion: result.engineVersion ?? '',
|
|
65
|
+
roundtripStatus: result.status,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildCodegenIntegrityEvidence({ taskId, integrityHash, artifacts, summary }) {
|
|
71
|
+
return buildCodegenEvidenceRecord({
|
|
72
|
+
kind: 'integrity',
|
|
73
|
+
taskId,
|
|
74
|
+
summary: summary ?? `Codegen integrity hash ${integrityHash}`,
|
|
75
|
+
artifacts,
|
|
76
|
+
details: { integrityHash },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseLegacyEvidenceForCodegen(evidenceLines = []) {
|
|
81
|
+
const records = [];
|
|
82
|
+
for (const line of evidenceLines) {
|
|
83
|
+
const text = String(line);
|
|
84
|
+
if (/round[- ]?trip/iu.test(text)) {
|
|
85
|
+
records.push({ kind: 'roundtrip', summary: text });
|
|
86
|
+
} else if (/integrity|hash|drift/iu.test(text)) {
|
|
87
|
+
records.push({ kind: 'integrity', summary: text });
|
|
88
|
+
} else if (/generated test|codegen test/iu.test(text)) {
|
|
89
|
+
records.push({ kind: 'generated_test', summary: text });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return records;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function evaluateCodegenVerifyGate(item, options = {}) {
|
|
97
|
+
const diagnostics = [];
|
|
98
|
+
const targetStatus = options.targetStatus ?? item.status;
|
|
99
|
+
|
|
100
|
+
if (!isCodegenFacingWorkItem(item)) {
|
|
101
|
+
return { ok: true, diagnostics, codegenFacing: false };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const legacyRecords = parseLegacyEvidenceForCodegen(item.evidence ?? []);
|
|
105
|
+
const structuredRecords = options.codegenEvidence ?? [];
|
|
106
|
+
const allKinds = new Set([
|
|
107
|
+
...legacyRecords.map((record) => record.kind),
|
|
108
|
+
...structuredRecords.map((record) => record.kind),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const bracketDrift = evaluateBracketIrDrift(item, options);
|
|
112
|
+
diagnostics.push(...bracketDrift.diagnostics);
|
|
113
|
+
|
|
114
|
+
if (targetStatus === 'done' || targetStatus === 'verify') {
|
|
115
|
+
if (!allKinds.has('integrity') && item.labels?.['trace.codegen_integrity_hash']) {
|
|
116
|
+
diagnostics.push({
|
|
117
|
+
severity: 'error',
|
|
118
|
+
code: 'codegen.integrity_missing',
|
|
119
|
+
message: `WorkItem ${item.id} expects integrity evidence for codegen hash label.`,
|
|
120
|
+
actionable: 'Record integrity evidence after reviewing generated output.',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!allKinds.has('roundtrip') && item.labels?.['trace.codegen_source_step']) {
|
|
125
|
+
diagnostics.push({
|
|
126
|
+
severity: 'warning',
|
|
127
|
+
code: 'codegen.roundtrip_missing',
|
|
128
|
+
message: `WorkItem ${item.id} has codegen source step but no roundtrip evidence.`,
|
|
129
|
+
actionable: 'Run compiler round-trip and attach evidence before done.',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (item.labels?.['trace.codegen_integrity_hash'] && legacyRecords.some((record) => /drift|mismatch/iu.test(record.summary))) {
|
|
135
|
+
diagnostics.push({
|
|
136
|
+
severity: 'error',
|
|
137
|
+
code: 'codegen.integrity_drift',
|
|
138
|
+
message: `WorkItem ${item.id} reports generated code drift.`,
|
|
139
|
+
actionable: 'Re-run round-trip or update integrity hash after review.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const ok = diagnostics.every((diagnostic) => diagnostic.severity !== 'error');
|
|
144
|
+
return { ok, diagnostics, codegenFacing: true, bracketSignal: bracketDrift.signal };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function buildCodegenVerificationGate(items, options = {}) {
|
|
148
|
+
if (!Array.isArray(items)) {
|
|
149
|
+
throw new TypeError('items must be an array');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 24;
|
|
153
|
+
const evaluations = items
|
|
154
|
+
.map((item) => {
|
|
155
|
+
const gate = evaluateCodegenVerifyGate(item, { targetStatus: item.status });
|
|
156
|
+
if (!gate.codegenFacing) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
workId: item.id,
|
|
162
|
+
title: item.title ?? item.id,
|
|
163
|
+
status: item.status,
|
|
164
|
+
ok: gate.ok,
|
|
165
|
+
diagnostics: gate.diagnostics,
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
.sort((left, right) => compareText(left.workId, right.workId));
|
|
170
|
+
|
|
171
|
+
const failedCount = evaluations.filter((entry) => !entry.ok).length;
|
|
172
|
+
const passedCount = evaluations.length - failedCount;
|
|
173
|
+
let status = 'not_run';
|
|
174
|
+
if (evaluations.length > 0) {
|
|
175
|
+
status = failedCount === 0 ? 'passed' : 'failed';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
schema: 'verification.codegen-gate.v1',
|
|
180
|
+
status,
|
|
181
|
+
codegenFacingCount: evaluations.length,
|
|
182
|
+
passedCount,
|
|
183
|
+
failedCount,
|
|
184
|
+
truncated: evaluations.length > limit,
|
|
185
|
+
items: evaluations.slice(0, limit),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { buildCodegenRoundtripEvidence } from './codegenEvidence.mjs';
|
|
6
|
+
import {
|
|
7
|
+
formatStepAtomDraft,
|
|
8
|
+
parseStepAtomDrafts,
|
|
9
|
+
validateStepAtomDraft,
|
|
10
|
+
} from './stepAtomFormatter.mjs';
|
|
11
|
+
|
|
12
|
+
export const COMPILER_ROUNDTRIP_RESULT_SCHEMA = 'compiler.roundtrip.result.v1';
|
|
13
|
+
export const COMPILER_ROUNDTRIP_ENGINE_VERSION = 'workgraph-compiler-roundtrip-mvp.v1';
|
|
14
|
+
|
|
15
|
+
function isCompilerModeDraft(draft) {
|
|
16
|
+
const labels = draft?.labels ?? {};
|
|
17
|
+
return labels['compiler.mode'] !== undefined || draft?.profile === 'compiler';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runCompilerRoundTrip(options = {}) {
|
|
21
|
+
const cwd = options.cwd ?? process.cwd();
|
|
22
|
+
const stepPath = String(options.stepPath ?? '').trim();
|
|
23
|
+
|
|
24
|
+
if (stepPath === '') {
|
|
25
|
+
throw new TypeError('stepPath is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const absoluteStepPath = resolve(cwd, stepPath);
|
|
29
|
+
const command = options.command ?? `npm run compiler:roundtrip -- ${stepPath.replace(/\\/g, '/')}`;
|
|
30
|
+
const stepText = options.stepText ?? await readFile(absoluteStepPath, 'utf8');
|
|
31
|
+
const imports = parseStepAtomDrafts(stepText);
|
|
32
|
+
const primary = imports[0];
|
|
33
|
+
|
|
34
|
+
if (!primary?.draft || !isCompilerModeDraft(primary.draft)) {
|
|
35
|
+
const result = {
|
|
36
|
+
schema: COMPILER_ROUNDTRIP_RESULT_SCHEMA,
|
|
37
|
+
stepPath: stepPath.replace(/\\/g, '/'),
|
|
38
|
+
generatedPaths: [],
|
|
39
|
+
status: 'skipped',
|
|
40
|
+
command,
|
|
41
|
+
exitCode: 0,
|
|
42
|
+
diffSummary: 'Step is not compiler-mode; round-trip skipped.',
|
|
43
|
+
engineVersion: COMPILER_ROUNDTRIP_ENGINE_VERSION,
|
|
44
|
+
taskId: options.taskId ?? null,
|
|
45
|
+
skippedReason: 'not_compiler_mode',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
result,
|
|
50
|
+
evidence: buildCodegenRoundtripEvidence(result),
|
|
51
|
+
validationErrors: [],
|
|
52
|
+
warnings: primary?.warnings ?? [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const validationErrors = [
|
|
57
|
+
...(primary.errors ?? []),
|
|
58
|
+
...validateStepAtomDraft(primary.draft),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
if (validationErrors.length > 0) {
|
|
62
|
+
const result = {
|
|
63
|
+
schema: COMPILER_ROUNDTRIP_RESULT_SCHEMA,
|
|
64
|
+
stepPath: stepPath.replace(/\\/g, '/'),
|
|
65
|
+
generatedPaths: [],
|
|
66
|
+
status: 'failed',
|
|
67
|
+
command,
|
|
68
|
+
exitCode: 1,
|
|
69
|
+
diffSummary: `StepAtomDraft validation failed: ${validationErrors.join('; ')}`,
|
|
70
|
+
engineVersion: COMPILER_ROUNDTRIP_ENGINE_VERSION,
|
|
71
|
+
taskId: options.taskId ?? primary.draft.labels?.['work.id'] ?? primary.draft.name,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
result,
|
|
76
|
+
evidence: buildCodegenRoundtripEvidence(result),
|
|
77
|
+
validationErrors,
|
|
78
|
+
warnings: primary.warnings ?? [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const formattedOnce = formatStepAtomDraft(primary.draft);
|
|
83
|
+
const reparsed = parseStepAtomDrafts(formattedOnce)[0];
|
|
84
|
+
const formattedTwice = formatStepAtomDraft(reparsed.draft);
|
|
85
|
+
const stable = formattedOnce === formattedTwice;
|
|
86
|
+
const reparsedErrors = [
|
|
87
|
+
...(reparsed.errors ?? []),
|
|
88
|
+
...validateStepAtomDraft(reparsed.draft),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const result = {
|
|
92
|
+
schema: COMPILER_ROUNDTRIP_RESULT_SCHEMA,
|
|
93
|
+
stepPath: stepPath.replace(/\\/g, '/'),
|
|
94
|
+
generatedPaths: [stepPath.replace(/\\/g, '/')],
|
|
95
|
+
status: stable && reparsedErrors.length === 0 ? 'passed' : 'failed',
|
|
96
|
+
command,
|
|
97
|
+
exitCode: stable && reparsedErrors.length === 0 ? 0 : 1,
|
|
98
|
+
diffSummary: stable && reparsedErrors.length === 0
|
|
99
|
+
? 'format(parse(format(draft))) stable for compiler-mode step'
|
|
100
|
+
: 'Round-trip invariant failed for compiler-mode step',
|
|
101
|
+
engineVersion: COMPILER_ROUNDTRIP_ENGINE_VERSION,
|
|
102
|
+
taskId: options.taskId ?? primary.draft.labels?.['work.id'] ?? primary.draft.name,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
result,
|
|
107
|
+
evidence: buildCodegenRoundtripEvidence(result),
|
|
108
|
+
validationErrors: reparsedErrors,
|
|
109
|
+
warnings: [...(primary.warnings ?? []), ...(reparsed.warnings ?? [])],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function runCompilerRoundTripCli(argv = process.argv) {
|
|
114
|
+
const args = argv.slice(2);
|
|
115
|
+
let stepPath;
|
|
116
|
+
let taskId;
|
|
117
|
+
let jsonOnly = false;
|
|
118
|
+
|
|
119
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
120
|
+
const arg = args[index];
|
|
121
|
+
if (arg === '--json') {
|
|
122
|
+
jsonOnly = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (arg === '--task-id') {
|
|
126
|
+
taskId = args[index + 1];
|
|
127
|
+
index += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!arg.startsWith('-') && stepPath === undefined) {
|
|
131
|
+
stepPath = arg;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!stepPath) {
|
|
136
|
+
stepPath = 'tests/fixtures/compiler-roundtrip/sample.compiler.bvc';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const output = await runCompilerRoundTrip({ stepPath, taskId });
|
|
140
|
+
|
|
141
|
+
if (jsonOnly) {
|
|
142
|
+
console.log(JSON.stringify({
|
|
143
|
+
result: output.result,
|
|
144
|
+
evidence: output.evidence,
|
|
145
|
+
validationErrors: output.validationErrors,
|
|
146
|
+
warnings: output.warnings,
|
|
147
|
+
}, null, 2));
|
|
148
|
+
} else {
|
|
149
|
+
console.log(JSON.stringify({
|
|
150
|
+
schema: output.result.schema,
|
|
151
|
+
status: output.result.status,
|
|
152
|
+
stepPath: output.result.stepPath,
|
|
153
|
+
evidenceId: output.evidence.id,
|
|
154
|
+
diffSummary: output.result.diffSummary,
|
|
155
|
+
}, null, 2));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return output.result.status === 'failed' ? 1 : 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
162
|
+
const exitCode = await runCompilerRoundTripCli();
|
|
163
|
+
process.exitCode = exitCode;
|
|
164
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import dagre from '@dagrejs/dagre';
|
|
2
|
+
import { resolveGraphCanvasOverlaps } from './graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* nodesep?: number,
|
|
7
|
+
* ranksep?: number,
|
|
8
|
+
* edgesep?: number,
|
|
9
|
+
* marginx?: number,
|
|
10
|
+
* marginy?: number,
|
|
11
|
+
* rankdir?: 'TB' | 'BT' | 'LR' | 'RL',
|
|
12
|
+
* ranker?: 'network-simplex' | 'tight-tree' | 'longest-path',
|
|
13
|
+
* align?: 'UL' | 'UR' | 'DL' | 'DR',
|
|
14
|
+
* resolveOverlaps?: boolean,
|
|
15
|
+
* overlapGap?: number,
|
|
16
|
+
* }} DagreLayoutOptions
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Array<{ id: string, width: number, height: number, [key: string]: unknown }>} nodes
|
|
21
|
+
* @param {Array<{ from: string, to: string }>} edges
|
|
22
|
+
* @param {DagreLayoutOptions} [options]
|
|
23
|
+
*/
|
|
24
|
+
export function layoutGraphWithDagre(nodes, edges, options = {}) {
|
|
25
|
+
const rankdir = options.rankdir ?? 'TB';
|
|
26
|
+
const graph = new dagre.graphlib.Graph({ compound: false, directed: true });
|
|
27
|
+
graph.setGraph({
|
|
28
|
+
rankdir,
|
|
29
|
+
nodesep: options.nodesep ?? 56,
|
|
30
|
+
ranksep: options.ranksep ?? 112,
|
|
31
|
+
edgesep: options.edgesep ?? 24,
|
|
32
|
+
marginx: options.marginx ?? 32,
|
|
33
|
+
marginy: options.marginy ?? 32,
|
|
34
|
+
ranker: options.ranker ?? 'network-simplex',
|
|
35
|
+
align: options.align ?? 'UL',
|
|
36
|
+
});
|
|
37
|
+
graph.setDefaultEdgeLabel(() => ({}));
|
|
38
|
+
|
|
39
|
+
for (const node of nodes) {
|
|
40
|
+
graph.setNode(node.id, { width: node.width, height: node.height });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const edge of edges) {
|
|
44
|
+
if (graph.hasNode(edge.from) && graph.hasNode(edge.to)) {
|
|
45
|
+
graph.setEdge(edge.from, edge.to);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dagre.layout(graph);
|
|
50
|
+
|
|
51
|
+
const isHorizontal = rankdir === 'LR' || rankdir === 'RL';
|
|
52
|
+
const rankByCoord = new Map();
|
|
53
|
+
const sortedRanks = [...new Set(nodes.map((node) => graph.node(node.id)[isHorizontal ? 'x' : 'y']))]
|
|
54
|
+
.sort((left, right) => left - right);
|
|
55
|
+
sortedRanks.forEach((value, index) => rankByCoord.set(value, index));
|
|
56
|
+
|
|
57
|
+
let placed = nodes.map((node) => {
|
|
58
|
+
const positioned = graph.node(node.id);
|
|
59
|
+
const rankCoord = isHorizontal ? positioned.x : positioned.y;
|
|
60
|
+
const crossCoord = isHorizontal ? positioned.y : positioned.x;
|
|
61
|
+
return {
|
|
62
|
+
...node,
|
|
63
|
+
x: positioned.x - node.width / 2,
|
|
64
|
+
y: positioned.y - node.height / 2,
|
|
65
|
+
row: isHorizontal ? crossCoord : (rankByCoord.get(rankCoord) ?? 0),
|
|
66
|
+
col: isHorizontal ? (rankByCoord.get(rankCoord) ?? 0) : crossCoord,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (options.resolveOverlaps !== false) {
|
|
71
|
+
placed = resolveGraphCanvasOverlaps(placed, {
|
|
72
|
+
gap: options.overlapGap ?? 28,
|
|
73
|
+
layoutDirection: rankdir,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return placed;
|
|
78
|
+
}
|