@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.
Files changed (194) hide show
  1. package/README.md +31 -0
  2. package/bin/work-graph.mjs +238 -0
  3. package/package.json +38 -0
  4. package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
  5. package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
  6. package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
  7. package/vendor/packages/workgraph-mcp/README.md +28 -0
  8. package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
  9. package/vendor/packages/workgraph-mcp/package.json +37 -0
  10. package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
  11. package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
  12. package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
  13. package/vendor/public/assets/workgraph-logo.svg +11 -0
  14. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
  15. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
  16. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
  17. package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
  18. package/vendor/public/graph-canvas-lit-flow.css +154 -0
  19. package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
  20. package/vendor/public/graph-canvas-lit-flow.js +8530 -0
  21. package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
  22. package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
  23. package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
  24. package/vendor/src/agentRunApi.mjs +136 -0
  25. package/vendor/src/agentToolLoopGuard.mjs +88 -0
  26. package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
  27. package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
  28. package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
  29. package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
  30. package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
  31. package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
  32. package/vendor/src/analyticsPanelProjection.mjs +212 -0
  33. package/vendor/src/analyticsRecordStore.mjs +165 -0
  34. package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
  35. package/vendor/src/architectureL1Canon.mjs +419 -0
  36. package/vendor/src/architectureLayout.mjs +229 -0
  37. package/vendor/src/architectureSnapshot.mjs +490 -0
  38. package/vendor/src/architectureViewsProjection.mjs +116 -0
  39. package/vendor/src/atomInspector.mjs +253 -0
  40. package/vendor/src/atomInspectorApi.mjs +130 -0
  41. package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
  42. package/vendor/src/backlogSchemaLint.mjs +176 -0
  43. package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
  44. package/vendor/src/bracketIrTraceSignal.mjs +93 -0
  45. package/vendor/src/bvcAtomParser.mjs +210 -0
  46. package/vendor/src/bvcDialectRegistry.mjs +86 -0
  47. package/vendor/src/bvcFileFormat.mjs +218 -0
  48. package/vendor/src/bvcFormatCli.mjs +55 -0
  49. package/vendor/src/bvcLintCli.mjs +48 -0
  50. package/vendor/src/bvcNewWritePolicy.mjs +70 -0
  51. package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
  52. package/vendor/src/claimNoEligibleEval.mjs +205 -0
  53. package/vendor/src/closingAnalysisSuggest.mjs +59 -0
  54. package/vendor/src/codeGapAnalyzer.mjs +308 -0
  55. package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
  56. package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
  57. package/vendor/src/codeGapOperatorProjection.mjs +60 -0
  58. package/vendor/src/codeSyntaxHighlight.mjs +123 -0
  59. package/vendor/src/codegenEvidence.mjs +187 -0
  60. package/vendor/src/compilerRoundTripCli.mjs +164 -0
  61. package/vendor/src/dagreGraphLayout.mjs +78 -0
  62. package/vendor/src/draftIntakePromotionRules.mjs +205 -0
  63. package/vendor/src/epicWorkScope.mjs +85 -0
  64. package/vendor/src/evalLiveLlmEnv.mjs +63 -0
  65. package/vendor/src/evidenceReadModel.mjs +167 -0
  66. package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
  67. package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
  68. package/vendor/src/goldenPath.mjs +69 -0
  69. package/vendor/src/graphCanvasLayout.mjs +464 -0
  70. package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
  71. package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
  72. package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
  73. package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
  74. package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
  75. package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
  76. package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
  77. package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
  78. package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
  79. package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
  80. package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
  81. package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
  82. package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
  83. package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
  84. package/vendor/src/graphRagContextSlice.mjs +461 -0
  85. package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
  86. package/vendor/src/homeSnapshotApi.mjs +131 -0
  87. package/vendor/src/homeSnapshotProjection.mjs +275 -0
  88. package/vendor/src/inboxEventStream.mjs +140 -0
  89. package/vendor/src/intentComposerApi.mjs +245 -0
  90. package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
  91. package/vendor/src/intentGraphProjection.mjs +208 -0
  92. package/vendor/src/intentHierarchy.mjs +241 -0
  93. package/vendor/src/intentNodeLint.mjs +107 -0
  94. package/vendor/src/intentNodeRuntime.mjs +185 -0
  95. package/vendor/src/intentRoadmapCanvas.mjs +393 -0
  96. package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
  97. package/vendor/src/intentRoadmapMermaid.mjs +165 -0
  98. package/vendor/src/intentRoadmapProjection.mjs +85 -0
  99. package/vendor/src/intentTreeLint.mjs +114 -0
  100. package/vendor/src/intentTreeMigration.mjs +150 -0
  101. package/vendor/src/intentTreeWorkItems.mjs +227 -0
  102. package/vendor/src/kanbanBoardProjection.mjs +58 -0
  103. package/vendor/src/languageAdapterRegistry.mjs +180 -0
  104. package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
  105. package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
  106. package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
  107. package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
  108. package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
  109. package/vendor/src/languageAdapters/shared.mjs +68 -0
  110. package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
  111. package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
  112. package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
  113. package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
  114. package/vendor/src/markdownDocumentRender.mjs +208 -0
  115. package/vendor/src/memoryPanelProjection.mjs +116 -0
  116. package/vendor/src/memoryRecordWriter.mjs +243 -0
  117. package/vendor/src/memoryWorkerSlice.mjs +238 -0
  118. package/vendor/src/migrateStepToBvc.mjs +133 -0
  119. package/vendor/src/missionControlServerHandlers.mjs +195 -0
  120. package/vendor/src/missionControlUiClient.mjs +278 -0
  121. package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
  122. package/vendor/src/onebaseCliRunner.mjs +145 -0
  123. package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
  124. package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
  125. package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
  126. package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
  127. package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
  128. package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
  129. package/vendor/src/onebaseWorkerTools.mjs +586 -0
  130. package/vendor/src/operatorShellProjection.mjs +102 -0
  131. package/vendor/src/pipelineProseRender.mjs +180 -0
  132. package/vendor/src/pipelineStageLint.mjs +118 -0
  133. package/vendor/src/promptRulesEditorApi.mjs +174 -0
  134. package/vendor/src/promptRulesProjection.mjs +134 -0
  135. package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
  136. package/vendor/src/pvrgTaskScope.mjs +152 -0
  137. package/vendor/src/releaseGateMatrix.mjs +188 -0
  138. package/vendor/src/schematicView.mjs +305 -0
  139. package/vendor/src/seedAnalyticsRecord.mjs +217 -0
  140. package/vendor/src/semanticSearchBm25.mjs +103 -0
  141. package/vendor/src/semanticSearchExcerpts.mjs +68 -0
  142. package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
  143. package/vendor/src/semanticSearchWorkflow.mjs +366 -0
  144. package/vendor/src/stepAtomFormatter.mjs +413 -0
  145. package/vendor/src/stepGraphSlice.mjs +318 -0
  146. package/vendor/src/ui/atoms/badge.mjs +40 -0
  147. package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
  148. package/vendor/src/ui/atoms/button.mjs +114 -0
  149. package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
  150. package/vendor/src/ui/atoms/icon.mjs +23 -0
  151. package/vendor/src/ui/atoms/input.mjs +38 -0
  152. package/vendor/src/ui/atoms/modal.mjs +44 -0
  153. package/vendor/src/ui/atoms/select.mjs +98 -0
  154. package/vendor/src/ui/backlogShellButtons.mjs +238 -0
  155. package/vendor/src/ui/htmlEscape.mjs +11 -0
  156. package/vendor/src/ui/molecules/rating.mjs +48 -0
  157. package/vendor/src/ui/molecules/tabs.mjs +70 -0
  158. package/vendor/src/ui/organisms/modal.mjs +1 -0
  159. package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
  160. package/vendor/src/ui/workItemStatusTone.mjs +36 -0
  161. package/vendor/src/unifiedLinkageProjection.mjs +264 -0
  162. package/vendor/src/verificationLoop.mjs +206 -0
  163. package/vendor/src/workGraphBacklogPersist.mjs +234 -0
  164. package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
  165. package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
  166. package/vendor/src/workGraphCycleSlice.mjs +184 -0
  167. package/vendor/src/workGraphDaemonTick.mjs +307 -0
  168. package/vendor/src/workGraphDaemonWatch.mjs +157 -0
  169. package/vendor/src/workGraphEngineRoot.mjs +136 -0
  170. package/vendor/src/workGraphInstallLayout.mjs +65 -0
  171. package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
  172. package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
  173. package/vendor/src/workGraphProjectHost.mjs +149 -0
  174. package/vendor/src/workGraphProjectInit.mjs +392 -0
  175. package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
  176. package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
  177. package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
  178. package/vendor/src/workGraphRuntime.mjs +1008 -0
  179. package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
  180. package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
  181. package/vendor/src/workGraphWorkerProvider.mjs +600 -0
  182. package/vendor/src/workItemBvcQuality.mjs +262 -0
  183. package/vendor/src/workItemCreateAnalysis.mjs +157 -0
  184. package/vendor/src/workItemDecisionPipeline.mjs +278 -0
  185. package/vendor/src/workItemEpicCascade.mjs +176 -0
  186. package/vendor/src/workItemExecutionGate.mjs +78 -0
  187. package/vendor/src/workItemHierarchy.mjs +226 -0
  188. package/vendor/src/workItemProseLint.mjs +133 -0
  189. package/vendor/src/workItemTextRusify.mjs +794 -0
  190. package/vendor/src/workItemTraceEnvelope.mjs +158 -0
  191. package/vendor/src/workItemUiReferences.mjs +272 -0
  192. package/vendor/src/workflowEpicGrouping.mjs +67 -0
  193. package/vendor/src/workflowTreeProjection.mjs +53 -0
  194. package/vendor/src/workspaceRegistry.mjs +150 -0
@@ -0,0 +1,490 @@
1
+ import {
2
+ ARCHITECTURE_L1_BLOCKS,
3
+ ARCHITECTURE_L1_CANON_REPO_ROOT,
4
+ ARCHITECTURE_L1_EDGES,
5
+ loadArchitectureL1Canon,
6
+ toArchitectureL1BlockProjection,
7
+ } from './architectureL1Canon.mjs';
8
+ import {
9
+ buildOnebasePvrgGraphFromProjectRoot,
10
+ mergeOnebaseGraphIntoBlockL2Graph,
11
+ } from './onebasePvrgGraphNodes.mjs';
12
+ import { ARCHITECTURE_LAYOUT_PROFILE } from './graphCanvasLayout.mjs';
13
+
14
+ export { ARCHITECTURE_L1_BLOCKS, ARCHITECTURE_L1_EDGES };
15
+
16
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
17
+
18
+ export function classifyWorkItemBlock(item) {
19
+ const searchable = [
20
+ item.id,
21
+ item.title,
22
+ item.department,
23
+ item.ownerRole,
24
+ item.nextAction,
25
+ ...(item.targetFiles ?? []),
26
+ ]
27
+ .join(' ')
28
+ .toLowerCase();
29
+
30
+ if (
31
+ item.department === 'domain-marketplace'
32
+ || item.department === 'domain-onebase'
33
+ || searchable.includes('intent/domains/marketplace')
34
+ || searchable.includes('intent/domains/onebase')
35
+ || searchable.includes('onebase')
36
+ || searchable.includes('../onebase')
37
+ || searchable.includes('marketplace')
38
+ ) {
39
+ return 'domains';
40
+ }
41
+
42
+ if (
43
+ searchable.includes('memory') ||
44
+ searchable.includes('claude-note') ||
45
+ item.department === 'knowledge-publishing'
46
+ ) {
47
+ return 'project-memory';
48
+ }
49
+
50
+ if (
51
+ searchable.includes('trace') ||
52
+ searchable.includes('evidence') ||
53
+ searchable.includes('verification')
54
+ ) {
55
+ return 'trace-evidence';
56
+ }
57
+
58
+ if (
59
+ searchable.includes('worker') ||
60
+ searchable.includes('agent-worker') ||
61
+ searchable.includes('agent-runtime') ||
62
+ searchable.includes('design-agent-') ||
63
+ searchable.includes('implement-agent-') ||
64
+ searchable.includes('runner') ||
65
+ searchable.includes('promptpilot')
66
+ ) {
67
+ return 'agent-runtime';
68
+ }
69
+
70
+ if (
71
+ searchable.includes('step-atom') ||
72
+ searchable.includes('formatter') ||
73
+ searchable.includes('charter') ||
74
+ searchable.includes('parser-roundtrip')
75
+ ) {
76
+ return 'step-canon';
77
+ }
78
+
79
+ if (
80
+ item.department === 'frontend-ui' ||
81
+ /\b(ui|dashboard|board|graph-viewer|atom-inspector|architecture|schematic)\b/u.test(searchable) ||
82
+ searchable.includes('pvrg') ||
83
+ searchable.includes('derived-graph')
84
+ ) {
85
+ return 'derived-projections';
86
+ }
87
+
88
+ return 'work-graph';
89
+ }
90
+
91
+ function countTasksByStatus(taskIds, itemById) {
92
+ const counts = {};
93
+ for (const taskId of taskIds) {
94
+ const status = itemById.get(taskId)?.status ?? 'missing';
95
+ counts[status] = (counts[status] ?? 0) + 1;
96
+ }
97
+ return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => compareText(left, right)));
98
+ }
99
+
100
+ function collectArtifactPaths(blockId, items) {
101
+ const paths = new Set();
102
+ for (const item of items) {
103
+ if (classifyWorkItemBlock(item) !== blockId) {
104
+ continue;
105
+ }
106
+ for (const path of item.targetFiles ?? []) {
107
+ paths.add(path);
108
+ }
109
+ }
110
+ return [...paths].sort(compareText);
111
+ }
112
+
113
+ export const ARCHITECTURE_L2_MAX_NODES = 15;
114
+ export const L2_CONTAINER_HEIGHT = 104;
115
+ export const L2_FILE_HEIGHT = 82;
116
+ export const L2_ROW_GAP = 24;
117
+ export const L2_COLUMN_GAP = 184;
118
+ export const L2_CONTAINER_WIDTH = 252;
119
+ export const L2_FILE_WIDTH = 312;
120
+ export const L2_LAYOUT_TOP_PADDING = 24;
121
+
122
+ function normalizePathPrefix(path) {
123
+ return String(path).replace(/\\/g, '/');
124
+ }
125
+
126
+ function pathMatchesPrefix(path, prefix) {
127
+ const normalizedPath = normalizePathPrefix(path);
128
+ const normalizedPrefix = normalizePathPrefix(prefix);
129
+ if (normalizedPath === normalizedPrefix) {
130
+ return true;
131
+ }
132
+ if (normalizedPrefix.endsWith('/')) {
133
+ return normalizedPath.startsWith(normalizedPrefix);
134
+ }
135
+ return normalizedPath.startsWith(`${normalizedPrefix}/`) || normalizedPath.startsWith(normalizedPrefix);
136
+ }
137
+
138
+ function findContainerForPath(containers, path) {
139
+ let best = null;
140
+
141
+ for (const container of containers) {
142
+ for (const containerPath of container.paths ?? []) {
143
+ if (!pathMatchesPrefix(path, containerPath) && !pathMatchesPrefix(containerPath, path)) {
144
+ continue;
145
+ }
146
+
147
+ const score = normalizePathPrefix(containerPath).length;
148
+ if (!best || score > best.score) {
149
+ best = { container, score };
150
+ }
151
+ }
152
+ }
153
+
154
+ return best?.container ?? null;
155
+ }
156
+
157
+ function containerEdgeType(kind) {
158
+ if (kind === 'protocol') {
159
+ return 'defines';
160
+ }
161
+ if (kind === 'runtime' || kind === 'ui') {
162
+ return 'implements';
163
+ }
164
+ return 'uses';
165
+ }
166
+
167
+ function ensureFileNode(nodeById, path) {
168
+ const id = `file:${path}`;
169
+ if (!nodeById.has(id)) {
170
+ nodeById.set(id, {
171
+ id,
172
+ kind: 'file',
173
+ title: path.split('/').pop() || path,
174
+ subtitle: 'artifact',
175
+ path,
176
+ });
177
+ }
178
+ return nodeById.get(id);
179
+ }
180
+
181
+ export function buildArchitectureBlockL2Graph(block, workItems = [], options = {}) {
182
+ if (!block || typeof block.id !== 'string') {
183
+ throw new TypeError('block must include id');
184
+ }
185
+
186
+ const maxNodes = options.maxNodes ?? ARCHITECTURE_L2_MAX_NODES;
187
+ const containers = block.containers ?? [];
188
+ const nodeById = new Map();
189
+ const edges = [];
190
+ const edgeKeys = new Set();
191
+
192
+ for (const container of containers) {
193
+ nodeById.set(`container:${container.id}`, {
194
+ id: `container:${container.id}`,
195
+ kind: 'container',
196
+ title: container.title,
197
+ subtitle: container.kind,
198
+ paths: [...(container.paths ?? [])].sort(compareText),
199
+ ...(container.basis ? { basis: container.basis } : {}),
200
+ ...(container.vector ? { vector: container.vector } : {}),
201
+ ...(container.goal ? { goal: container.goal } : {}),
202
+ ...(container.analysis ? { analysis: container.analysis } : {}),
203
+ ...(container.decision ? { decision: container.decision } : {}),
204
+ ...(container.labels && Object.keys(container.labels).length > 0 ? { labels: { ...container.labels } } : {}),
205
+ });
206
+
207
+ for (const path of container.paths ?? []) {
208
+ const fileNode = ensureFileNode(nodeById, path);
209
+ const edgeKey = `${container.id}->${fileNode.id}:${containerEdgeType(container.kind)}`;
210
+ if (!edgeKeys.has(edgeKey)) {
211
+ edgeKeys.add(edgeKey);
212
+ edges.push({
213
+ from: `container:${container.id}`,
214
+ to: fileNode.id,
215
+ type: containerEdgeType(container.kind),
216
+ });
217
+ }
218
+ }
219
+ }
220
+
221
+ const artifactPaths = [...new Set([
222
+ ...(block.artifactPaths ?? []),
223
+ ...workItems.flatMap((item) => item.targetFiles ?? []),
224
+ ])].sort(compareText);
225
+
226
+ for (const path of artifactPaths) {
227
+ const container = findContainerForPath(containers, path);
228
+ if (!container) {
229
+ continue;
230
+ }
231
+
232
+ const fileNode = ensureFileNode(nodeById, path);
233
+ const edgeKey = `${container.id}->${fileNode.id}:relates_file`;
234
+ if (edgeKeys.has(edgeKey)) {
235
+ continue;
236
+ }
237
+
238
+ edgeKeys.add(edgeKey);
239
+ edges.push({
240
+ from: `container:${container.id}`,
241
+ to: fileNode.id,
242
+ type: 'relates_file',
243
+ });
244
+ }
245
+
246
+ const connectedIds = new Set(edges.flatMap((edge) => [edge.from, edge.to]));
247
+ const prunedNodes = [...nodeById.values()].filter(
248
+ (node) => node.kind === 'container' || connectedIds.has(node.id),
249
+ );
250
+ const containerNodes = prunedNodes.filter((node) => node.kind === 'container');
251
+ const fileNodes = prunedNodes.filter((node) => node.kind === 'file');
252
+ const hiddenCount = Math.max(0, prunedNodes.length - maxNodes);
253
+ const visibleNodes = [
254
+ ...containerNodes,
255
+ ...fileNodes.slice(0, Math.max(0, maxNodes - containerNodes.length)),
256
+ ];
257
+
258
+ const visibleIds = new Set(visibleNodes.map((node) => node.id));
259
+ const visibleEdges = edges
260
+ .filter((edge) => visibleIds.has(edge.from) && visibleIds.has(edge.to))
261
+ .sort((left, right) => compareText(`${left.from}\0${left.to}\0${left.type}`, `${right.from}\0${right.to}\0${right.type}`));
262
+
263
+ return {
264
+ schema: 'architecture.block_l2_graph.v1',
265
+ blockId: block.id,
266
+ nodes: visibleNodes,
267
+ edges: visibleEdges,
268
+ capped: hiddenCount > 0,
269
+ hiddenCount,
270
+ counts: {
271
+ nodes: visibleNodes.length,
272
+ edges: visibleEdges.length,
273
+ containers: containerNodes.length,
274
+ files: visibleNodes.filter((node) => node.kind === 'file').length,
275
+ },
276
+ };
277
+ }
278
+
279
+ export function layoutArchitectureL2Graph(graph) {
280
+ const containerNodes = graph.nodes.filter((node) => node.kind === 'container');
281
+ const fileNodes = graph.nodes.filter((node) => node.kind === 'file');
282
+ const nodeById = new Map();
283
+ const containerWidth = L2_CONTAINER_WIDTH;
284
+ const fileWidth = L2_FILE_WIDTH;
285
+ const containerX = 20;
286
+ const fileX = containerX + containerWidth + L2_COLUMN_GAP;
287
+
288
+ const containerLayouts = containerNodes.map((node) => ({
289
+ node,
290
+ width: containerWidth,
291
+ height: estimateL2NodeHeight(node, containerWidth, L2_CONTAINER_HEIGHT),
292
+ }));
293
+
294
+ const containerStackHeight = containerLayouts.reduce(
295
+ (sum, item, index) => sum + item.height + (index > 0 ? L2_ROW_GAP : 0),
296
+ 0,
297
+ );
298
+
299
+ let fileY = L2_LAYOUT_TOP_PADDING;
300
+ const fileLayouts = [];
301
+ for (const node of fileNodes) {
302
+ const height = estimateL2NodeHeight(node, fileWidth, L2_FILE_HEIGHT);
303
+ fileLayouts.push({ node, y: fileY, height });
304
+ nodeById.set(node.id, {
305
+ ...node,
306
+ depth: 1,
307
+ x: fileX,
308
+ y: fileY,
309
+ width: fileWidth,
310
+ height,
311
+ });
312
+ fileY += height + L2_ROW_GAP;
313
+ }
314
+
315
+ const fileColumnTop = L2_LAYOUT_TOP_PADDING;
316
+ const fileColumnBottom = fileLayouts.length > 0
317
+ ? fileLayouts[fileLayouts.length - 1].y + fileLayouts[fileLayouts.length - 1].height
318
+ : fileColumnTop + L2_FILE_HEIGHT;
319
+ const fileColumnCenter = fileColumnTop + (fileColumnBottom - fileColumnTop) / 2;
320
+
321
+ let containerY = fileLayouts.length > 0
322
+ ? Math.max(L2_LAYOUT_TOP_PADDING, fileColumnCenter - containerStackHeight / 2)
323
+ : L2_LAYOUT_TOP_PADDING;
324
+
325
+ for (const item of containerLayouts) {
326
+ nodeById.set(item.node.id, {
327
+ ...item.node,
328
+ depth: 0,
329
+ x: containerX,
330
+ y: containerY,
331
+ width: item.width,
332
+ height: item.height,
333
+ });
334
+ containerY += item.height + L2_ROW_GAP;
335
+ }
336
+
337
+ const layoutNodes = graph.nodes.map((node) => nodeById.get(node.id)).filter(Boolean);
338
+ const rawEdges = graph.edges.map((edge) => ({
339
+ ...edge,
340
+ fromNode: nodeById.get(edge.from),
341
+ toNode: nodeById.get(edge.to),
342
+ })).filter((edge) => edge.fromNode && edge.toNode);
343
+
344
+ const layoutEdges = assignBlockL2EdgeLanes(rawEdges);
345
+
346
+ return {
347
+ ...graph,
348
+ layoutNodes,
349
+ layoutEdges,
350
+ width: fileX + fileWidth + 32,
351
+ height: Math.max(containerY, fileY, L2_CONTAINER_HEIGHT + 48) + 16,
352
+ };
353
+ }
354
+
355
+ function estimateL2NodeHeight(node, width, minHeight) {
356
+ const contentWidth = Math.max(1, width - 28);
357
+ const averageCharWidth = node.kind === 'file' ? 10 : 8;
358
+ const approxCharsPerLine = Math.max(12, Math.floor(contentWidth / averageCharWidth));
359
+ const titleText = node.kind === 'file' ? (node.path || node.title || '') : (node.title || '');
360
+ const subtitleText = node.kind === 'file' ? '' : (node.subtitle || '');
361
+ const titleLines = Math.max(1, Math.ceil(String(titleText).length / approxCharsPerLine));
362
+ const subtitleLines = subtitleText ? Math.max(1, Math.ceil(String(subtitleText).length / approxCharsPerLine)) : 0;
363
+ const kindLine = 20;
364
+ const titleLineHeight = 22;
365
+ const subtitleLineHeight = 21;
366
+ const verticalPadding = 20;
367
+ return Math.max(minHeight, verticalPadding + kindLine + titleLines * titleLineHeight + subtitleLines * subtitleLineHeight);
368
+ }
369
+
370
+ function assignBlockL2EdgeLanes(edges) {
371
+ const incoming = new Map();
372
+ edges.forEach((edge) => {
373
+ if (!incoming.has(edge.to)) incoming.set(edge.to, []);
374
+ incoming.get(edge.to).push(edge);
375
+ });
376
+
377
+ return edges.map((edge) => {
378
+ const inList = incoming.get(edge.to) ?? [];
379
+ return {
380
+ ...edge,
381
+ inLane: inList.indexOf(edge),
382
+ inLaneCount: inList.length,
383
+ };
384
+ });
385
+ }
386
+
387
+ export function buildArchitectureSnapshot(workGraphSnapshot, options = {}) {
388
+ if (!workGraphSnapshot || workGraphSnapshot.schema !== 'workgraph.snapshot.v1') {
389
+ throw new TypeError('workGraphSnapshot must be workgraph.snapshot.v1');
390
+ }
391
+
392
+ const repoRoot = options.repoRoot ?? ARCHITECTURE_L1_CANON_REPO_ROOT;
393
+ const canon = loadArchitectureL1Canon(repoRoot, { canonPath: options.canonPath });
394
+ const l1Blocks = canon.blocks.map(toArchitectureL1BlockProjection);
395
+
396
+ const items = [...workGraphSnapshot.items].sort((left, right) => compareText(left.id, right.id));
397
+ const itemById = new Map(items.map((item) => [item.id, item]));
398
+ const tasksByBlock = new Map(l1Blocks.map((block) => [block.id, []]));
399
+
400
+ for (const item of items) {
401
+ const blockId = classifyWorkItemBlock(item);
402
+ tasksByBlock.get(blockId)?.push(item.id);
403
+ }
404
+
405
+ for (const taskIds of tasksByBlock.values()) {
406
+ taskIds.sort(compareText);
407
+ }
408
+
409
+ const blocks = canon.blocks.map((canonBlock) => {
410
+ const block = toArchitectureL1BlockProjection(canonBlock);
411
+ const taskIds = tasksByBlock.get(block.id) ?? [];
412
+ const blockItems = items.filter((item) => taskIds.includes(item.id));
413
+ const blockSnapshot = {
414
+ id: block.id,
415
+ title: block.title,
416
+ summary: block.summary,
417
+ ...(canonBlock.group ? { group: canonBlock.group } : {}),
418
+ basis: canonBlock.basis,
419
+ vector: canonBlock.vector,
420
+ goal: canonBlock.goal,
421
+ analysis: canonBlock.analysis ?? '',
422
+ decision: canonBlock.decision ?? '',
423
+ labels: canonBlock.labels ?? {},
424
+ layer: 'L1',
425
+ intentRoots: [...block.intentRoots].sort(compareText),
426
+ taskIds,
427
+ taskCounts: countTasksByStatus(taskIds, itemById),
428
+ containers: block.containers.map((container) => ({
429
+ ...container,
430
+ paths: [...container.paths].sort(compareText),
431
+ })),
432
+ artifactPaths: collectArtifactPaths(block.id, items),
433
+ };
434
+ blockSnapshot.l2Graph = layoutArchitectureL2Graph(
435
+ buildArchitectureBlockL2Graph(blockSnapshot, blockItems),
436
+ );
437
+ return blockSnapshot;
438
+ });
439
+
440
+ const edges = canon.edges.map((edge) => ({ ...edge }));
441
+
442
+ const focusBlockId = options.focusBlockId ?? null;
443
+ if (focusBlockId !== null && !blocks.some((block) => block.id === focusBlockId)) {
444
+ throw new RangeError(`Unknown focusBlockId: ${focusBlockId}`);
445
+ }
446
+
447
+ const onebaseGraph = options.onebaseGraph ?? buildOnebasePvrgGraphFromProjectRoot({
448
+ repoRoot: options.repoRoot ?? process.cwd(),
449
+ projectRoot: options.onebaseProjectRoot,
450
+ });
451
+
452
+ for (const block of blocks) {
453
+ if (block.id !== 'domains') {
454
+ continue;
455
+ }
456
+
457
+ block.onebaseGraph = onebaseGraph;
458
+ block.l2Graph = layoutArchitectureL2Graph(
459
+ mergeOnebaseGraphIntoBlockL2Graph(block.l2Graph, onebaseGraph, {
460
+ maxNodes: options.maxNodes ?? ARCHITECTURE_L2_MAX_NODES,
461
+ }),
462
+ );
463
+ }
464
+
465
+ const containerCount = blocks.reduce((sum, block) => sum + block.containers.length, 0);
466
+
467
+ return {
468
+ schema: 'architecture.snapshot.v1',
469
+ sourceSchema: 'workgraph.snapshot.v1',
470
+ source: workGraphSnapshot.source ?? '.bvc',
471
+ focusBlockId,
472
+ l1Canon: {
473
+ id: canon.passport?.id ?? canon.digest,
474
+ version: canon.passport?.version ?? 1,
475
+ digest: canon.digest,
476
+ sourcePath: canon.sourcePath,
477
+ charterRef: canon.passport?.charterRef ?? null,
478
+ protocolRef: canon.passport?.protocolRef ?? null,
479
+ },
480
+ blocks,
481
+ edges,
482
+ layoutProfile: options.layoutProfile ?? ARCHITECTURE_LAYOUT_PROFILE,
483
+ counts: {
484
+ blocks: blocks.length,
485
+ edges: edges.length,
486
+ tasks: items.length,
487
+ containers: containerCount,
488
+ },
489
+ };
490
+ }
@@ -0,0 +1,116 @@
1
+ const architectureViewsCompareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
2
+
3
+ export const ARCHITECTURE_L1_GROUP_LABELS = {
4
+ domains: 'Домены',
5
+ };
6
+
7
+ /**
8
+ * @param {{ group?: string }} block
9
+ */
10
+ export function getArchitectureBlockGroupLabel(block) {
11
+ const group = block?.group ?? '';
12
+ return ARCHITECTURE_L1_GROUP_LABELS[group] ?? '';
13
+ }
14
+
15
+ /**
16
+ * Row/detail title — block title only; L1 group is shown separately in the list section header.
17
+ * @param {{ id?: string, title?: string }} block
18
+ */
19
+ export function formatArchitectureBlockDisplayTitle(block) {
20
+ return block?.title ?? block?.id ?? '';
21
+ }
22
+
23
+ function mermaidNodeId(blockId) {
24
+ return String(blockId).replace(/[^a-zA-Z0-9_]/g, '_');
25
+ }
26
+
27
+ function escapeMermaidLabel(value) {
28
+ return String(value ?? '').replace(/"/g, "'").replace(/\[/g, '(').replace(/\]/g, ')');
29
+ }
30
+
31
+ /**
32
+ * @param {{ blocks?: Array<{ id: string, title?: string }>, edges?: Array<{ from: string, to: string, type?: string }> }} snapshot
33
+ */
34
+ export function exportArchitectureSnapshotMermaid(snapshot) {
35
+ const blocks = snapshot?.blocks ?? [];
36
+ const edges = snapshot?.edges ?? [];
37
+ const lines = ['flowchart LR'];
38
+
39
+ for (const block of blocks) {
40
+ const id = mermaidNodeId(block.id);
41
+ const label = escapeMermaidLabel(block.title ?? block.id);
42
+ lines.push(` ${id}["${label}"]`);
43
+ }
44
+
45
+ for (const edge of edges) {
46
+ const from = mermaidNodeId(edge.from);
47
+ const to = mermaidNodeId(edge.to);
48
+ const edgeLabel = escapeMermaidLabel(edge.type ?? '');
49
+ lines.push(edgeLabel
50
+ ? ` ${from} -->|"${edgeLabel}"| ${to}`
51
+ : ` ${from} --> ${to}`);
52
+ }
53
+
54
+ return `${lines.join('\n')}\n`;
55
+ }
56
+
57
+ const MATRIX_STATUS_COLUMNS = [
58
+ { id: 'backlog', label: 'Бэклог', statuses: ['backlog'] },
59
+ { id: 'ready', label: 'Готово', statuses: ['ready'] },
60
+ { id: 'active', label: 'В работе', statuses: ['claimed', 'doing', 'in_progress', 'verify', 'blocked'] },
61
+ { id: 'done', label: 'Завершено', statuses: ['done', 'verified'] },
62
+ ];
63
+
64
+ function sumStatuses(taskCounts, statuses) {
65
+ let total = 0;
66
+ for (const status of statuses) {
67
+ total += Number(taskCounts?.[status] ?? 0);
68
+ }
69
+ return total;
70
+ }
71
+
72
+ /**
73
+ * @param {{ blocks?: Array<{ id: string, title?: string, layer?: string, taskCounts?: Record<string, number> }> }} snapshot
74
+ */
75
+ export function buildArchitectureMatrixModel(snapshot) {
76
+ const blocks = [...(snapshot?.blocks ?? [])].sort((left, right) => architectureViewsCompareText(left.title ?? left.id, right.title ?? right.id));
77
+
78
+ return {
79
+ schema: 'architecture.matrix.v1',
80
+ columns: MATRIX_STATUS_COLUMNS.map((column) => ({ id: column.id, label: column.label })),
81
+ rows: blocks.map((block) => ({
82
+ blockId: block.id,
83
+ title: block.title ?? block.id,
84
+ layer: block.layer ?? 'L1',
85
+ cells: MATRIX_STATUS_COLUMNS.map((column) => ({
86
+ columnId: column.id,
87
+ count: sumStatuses(block.taskCounts ?? {}, column.statuses),
88
+ })),
89
+ })),
90
+ };
91
+ }
92
+
93
+ /**
94
+ * @param {{ id: string, title?: string, summary?: string, vector?: string, group?: string, taskIds?: string[], taskCounts?: Record<string, number> }} block
95
+ */
96
+ export function summarizeArchitectureBlockForList(block) {
97
+ const taskCount = block.taskIds?.length ?? 0;
98
+ const activeCount = sumStatuses(block.taskCounts ?? {}, MATRIX_STATUS_COLUMNS.find((c) => c.id === 'active')?.statuses ?? []);
99
+ const doneCount = sumStatuses(block.taskCounts ?? {}, MATRIX_STATUS_COLUMNS.find((c) => c.id === 'done')?.statuses ?? []);
100
+ const vectorPreview = String(block.vector ?? '')
101
+ .split('\n')
102
+ .map((line) => line.trim())
103
+ .find(Boolean) ?? '';
104
+ const title = block.title ?? block.id;
105
+
106
+ return {
107
+ blockId: block.id,
108
+ title,
109
+ listTitle: title,
110
+ groupLabel: getArchitectureBlockGroupLabel(block),
111
+ summary: block.summary || vectorPreview,
112
+ taskCount,
113
+ activeCount,
114
+ doneCount,
115
+ };
116
+ }