@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,98 @@
1
+ import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
2
+
3
+ /**
4
+ * @param {{ value?: string, label?: string, selected?: boolean }} option
5
+ */
6
+ function renderOption(option) {
7
+ if (typeof option === 'string') {
8
+ return `<option value="${escapeHtmlAttr(option)}">${escapeHtml(option)}</option>`;
9
+ }
10
+ const value = option.value ?? '';
11
+ const label = option.label ?? value;
12
+ const selected = option.selected ? ' selected' : '';
13
+ return `<option value="${escapeHtmlAttr(value)}"${selected}>${escapeHtml(label)}</option>`;
14
+ }
15
+
16
+ /**
17
+ * @param {{
18
+ * id?: string,
19
+ * name?: string,
20
+ * value?: string,
21
+ * options?: Array<string | { value?: string, label?: string, selected?: boolean }>,
22
+ * disabled?: boolean,
23
+ * hidden?: boolean,
24
+ * testId?: string,
25
+ * ariaLabel?: string,
26
+ * className?: string,
27
+ * }} props
28
+ */
29
+ export function renderUiSelect(props = {}) {
30
+ const options = props.options ?? [];
31
+ const optionsHtml = options.map((option) => {
32
+ if (typeof option === 'object' && option.value != null && props.value === option.value) {
33
+ return renderOption({ ...option, selected: true });
34
+ }
35
+ return renderOption(option);
36
+ }).join('');
37
+
38
+ const classes = ['wg-select', props.className].filter(Boolean).join(' ');
39
+ const attrs = [
40
+ `class="${escapeHtmlAttr(classes)}"`,
41
+ props.id ? `id="${escapeHtmlAttr(props.id)}"` : '',
42
+ props.name ? `name="${escapeHtmlAttr(props.name)}"` : '',
43
+ props.disabled ? 'disabled' : '',
44
+ props.hidden ? 'hidden' : '',
45
+ props.testId ? `data-testid="${escapeHtmlAttr(props.testId)}"` : '',
46
+ props.ariaLabel ? `aria-label="${escapeHtmlAttr(props.ariaLabel)}"` : '',
47
+ ].filter(Boolean).join(' ');
48
+
49
+ return `<select ${attrs}>${optionsHtml}</select>`;
50
+ }
51
+
52
+ export const UI_SELECT_CHEVRON_LIGHT = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235e6c84' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E\")";
53
+ export const UI_SELECT_CHEVRON_DARK = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239d9d9d' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E\")";
54
+
55
+ export const UI_SELECT_CSS = `
56
+ .wg-select {
57
+ box-sizing: border-box;
58
+ border: 1px solid var(--border);
59
+ border-radius: var(--ui-radius-control-sm, 0.25rem);
60
+ background-color: var(--panel-2);
61
+ color: var(--text);
62
+ padding: 8px 28px 8px 10px;
63
+ font: inherit;
64
+ font-size: 14px;
65
+ line-height: 1.2;
66
+ appearance: none;
67
+ -webkit-appearance: none;
68
+ color-scheme: light;
69
+ background-image: ${UI_SELECT_CHEVRON_LIGHT};
70
+ background-repeat: no-repeat;
71
+ background-size: 12px 12px;
72
+ background-position: right 8px center;
73
+ cursor: pointer;
74
+ }
75
+ .wg-select:hover {
76
+ background-color: var(--panel);
77
+ }
78
+ .wg-select:focus {
79
+ outline: none;
80
+ border-color: var(--accent);
81
+ background-color: var(--panel);
82
+ box-shadow: 0 0 0 1px var(--accent);
83
+ }
84
+ .wg-select:disabled {
85
+ opacity: 0.55;
86
+ cursor: not-allowed;
87
+ }
88
+ .wg-select--compact {
89
+ max-width: 160px;
90
+ }
91
+ body[data-theme="dark"] .wg-select {
92
+ color-scheme: dark;
93
+ background-image: ${UI_SELECT_CHEVRON_DARK};
94
+ }
95
+ .toolbar .wg-select {
96
+ flex-shrink: 0;
97
+ }
98
+ `;
@@ -0,0 +1,238 @@
1
+ import { renderUiButton } from './atoms/button.mjs';
2
+ import { renderUiSelect } from './atoms/select.mjs';
3
+ import { renderUiTabsGroup } from './molecules/tabs.mjs';
4
+
5
+ /**
6
+ * Sidebar nav tab (Gripe DS unstyled shell — keeps .nav-tab CSS).
7
+ * @param {{ view: string, label: string, selected?: boolean, disabled?: boolean }} props
8
+ */
9
+ export function renderNavTab({ view, label, selected = false, disabled = false }) {
10
+ return renderUiButton({
11
+ unstyled: true,
12
+ className: 'nav-tab',
13
+ label,
14
+ disabled,
15
+ attrs: {
16
+ 'data-view': view,
17
+ 'aria-selected': selected ? 'true' : 'false',
18
+ },
19
+ });
20
+ }
21
+
22
+ export function renderThemeToggleButton() {
23
+ return renderUiButton({
24
+ unstyled: true,
25
+ id: 'theme-toggle',
26
+ className: 'theme-toggle',
27
+ label: 'Тёмная тема',
28
+ attrs: { 'aria-pressed': 'false' },
29
+ });
30
+ }
31
+
32
+ export function renderDetailCloseButton() {
33
+ return renderUiButton({
34
+ unstyled: true,
35
+ id: 'detail-close',
36
+ className: 'detail-close',
37
+ label: 'Закрыть',
38
+ ariaLabel: 'Закрыть подробности',
39
+ });
40
+ }
41
+
42
+ export function renderDetailSubCloseButton() {
43
+ return renderUiButton({
44
+ unstyled: true,
45
+ id: 'detail-sub-close',
46
+ className: 'detail-close',
47
+ label: 'Закрыть',
48
+ ariaLabel: 'Закрыть описание узла L2',
49
+ });
50
+ }
51
+
52
+ export function renderAgentRunDockCloseButton() {
53
+ return renderUiButton({
54
+ unstyled: true,
55
+ id: 'agent-run-dock-close',
56
+ className: 'agent-dock-close',
57
+ label: '×',
58
+ ariaLabel: 'Свернуть dock',
59
+ });
60
+ }
61
+
62
+ export function renderAgentRunFooterButtons() {
63
+ return [
64
+ renderUiButton({
65
+ id: 'agent-run-retry',
66
+ variant: 'secondary',
67
+ size: 'sm',
68
+ label: 'Повтор',
69
+ }),
70
+ renderUiButton({
71
+ id: 'agent-run-open-task',
72
+ variant: 'primary',
73
+ size: 'sm',
74
+ label: 'Открыть задачу',
75
+ }),
76
+ ].join('\n ');
77
+ }
78
+
79
+ export function renderIntentComposerActionButtons() {
80
+ return [
81
+ renderUiButton({
82
+ variant: 'secondary',
83
+ size: 'sm',
84
+ label: 'Preview draft',
85
+ testId: 'intent-composer-propose',
86
+ attrs: { 'data-action': 'propose' },
87
+ }),
88
+ renderUiButton({
89
+ variant: 'primary',
90
+ size: 'sm',
91
+ label: 'Создать задачу',
92
+ testId: 'intent-composer-apply',
93
+ disabled: true,
94
+ attrs: { 'data-action': 'apply' },
95
+ }),
96
+ renderUiButton({
97
+ variant: 'flat',
98
+ size: 'sm',
99
+ label: 'Отменить',
100
+ testId: 'intent-composer-cancel',
101
+ attrs: { 'data-action': 'cancel' },
102
+ }),
103
+ ].join('\n ');
104
+ }
105
+
106
+ export function renderIntentDomainClearButton() {
107
+ return renderUiButton({
108
+ unstyled: true,
109
+ id: 'intent-domain-clear',
110
+ className: 'board-tab',
111
+ label: 'Сброс домена',
112
+ hidden: true,
113
+ });
114
+ }
115
+
116
+ export function renderSearchModeSelect() {
117
+ return renderUiSelect({
118
+ id: 'search-mode',
119
+ className: 'wg-select--search-mode',
120
+ testId: 'semantic-search-mode',
121
+ ariaLabel: 'Semantic search mode',
122
+ options: [
123
+ { value: 'local', label: 'local filter', selected: true },
124
+ { value: 'lexical-v1', label: 'semantic lexical' },
125
+ { value: 'hybrid-lexical-bm25-v1', label: 'semantic hybrid BM25' },
126
+ ],
127
+ });
128
+ }
129
+
130
+ export function renderCycleFilterSelect() {
131
+ return renderUiSelect({
132
+ id: 'cycle-filter',
133
+ className: 'wg-select--compact',
134
+ ariaLabel: 'Цикл',
135
+ options: [
136
+ { value: 'current', label: 'Текущий цикл', selected: true },
137
+ { value: 'all', label: 'Все циклы' },
138
+ ],
139
+ });
140
+ }
141
+
142
+ export function renderIntentDomainFilterSelect() {
143
+ return renderUiSelect({
144
+ id: 'intent-domain-filter',
145
+ className: 'wg-select--compact',
146
+ ariaLabel: 'Домен',
147
+ options: [{ value: '', label: 'Все домены', selected: true }],
148
+ });
149
+ }
150
+
151
+ export function renderAnalyticsSubtabsShell() {
152
+ return renderUiTabsGroup({
153
+ className: 'workflow-subtabs analytics-subtabs',
154
+ testId: 'analytics-subtabs',
155
+ ariaLabel: 'Тип аналитики',
156
+ tabs: [
157
+ {
158
+ id: 'intake',
159
+ label: 'Разборы',
160
+ selected: true,
161
+ count: 0,
162
+ elementId: 'analytics-tab-intake',
163
+ dataAttrKey: 'data-analytics-tab',
164
+ countId: 'analytics-intake-tab-count',
165
+ },
166
+ {
167
+ id: 'closing',
168
+ label: 'Итоги эпиков',
169
+ count: 0,
170
+ elementId: 'analytics-tab-closing',
171
+ dataAttrKey: 'data-analytics-tab',
172
+ countId: 'analytics-closing-tab-count',
173
+ },
174
+ ],
175
+ });
176
+ }
177
+
178
+ export function renderWorkflowSubtabsShell() {
179
+ return renderUiTabsGroup({
180
+ className: 'workflow-subtabs',
181
+ testId: 'workflow-subtabs',
182
+ ariaLabel: 'Бэклог и архив',
183
+ tabs: [
184
+ {
185
+ id: 'backlog',
186
+ label: 'Бэклог',
187
+ selected: true,
188
+ count: 0,
189
+ elementId: 'workflow-tab-backlog',
190
+ dataAttrKey: 'data-workflow-tab',
191
+ countId: 'workflow-backlog-tab-count',
192
+ },
193
+ {
194
+ id: 'archive',
195
+ label: 'Архив',
196
+ count: 0,
197
+ elementId: 'workflow-tab-archive',
198
+ dataAttrKey: 'data-workflow-tab',
199
+ countId: 'workflow-archive-tab-count',
200
+ },
201
+ ],
202
+ });
203
+ }
204
+
205
+ export function renderArchitectureSubtabsShell() {
206
+ return '';
207
+ }
208
+
209
+ export function renderWorkflowDisplayModeSelect() {
210
+ return renderUiSelect({
211
+ id: 'workflow-display-mode',
212
+ className: 'wg-select--compact',
213
+ ariaLabel: 'Режим списка задач',
214
+ options: [
215
+ { value: 'epic-groups', label: 'Эпики', selected: true },
216
+ { value: 'flat', label: 'Плоский' },
217
+ { value: 'tree', label: 'Дерево' },
218
+ ],
219
+ });
220
+ }
221
+
222
+ export function renderArchitectureGraphModeToggle() {
223
+ return (
224
+ '<div class="graph-canvas-mode-toggle" role="group" aria-label="Режим графа">' +
225
+ renderUiButton({
226
+ unstyled: true,
227
+ label: 'Конвейер',
228
+ className: 'is-active',
229
+ attrs: { 'data-graph-canvas-mode': 'pipeline', 'aria-pressed': 'true' },
230
+ }) +
231
+ renderUiButton({
232
+ unstyled: true,
233
+ label: 'Полный',
234
+ attrs: { 'data-graph-canvas-mode': 'full', 'aria-pressed': 'false' },
235
+ }) +
236
+ '</div>'
237
+ );
238
+ }
@@ -0,0 +1,11 @@
1
+ export function escapeHtml(value) {
2
+ return String(value ?? '')
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;');
7
+ }
8
+
9
+ export function escapeHtmlAttr(value) {
10
+ return escapeHtml(value).replace(/'/g, '&#39;');
11
+ }
@@ -0,0 +1,48 @@
1
+ import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
2
+
3
+ const SIZE_CLASS = {
4
+ xs: 'wg-rating--xs',
5
+ sm: 'wg-rating--sm',
6
+ md: 'wg-rating--md',
7
+ card: 'wg-rating--card',
8
+ lg: 'wg-rating--lg',
9
+ xl: 'wg-rating--xl',
10
+ };
11
+
12
+ const COLOR_CLASS = {
13
+ blue: 'wg-rating--blue',
14
+ 'yellow-orange': 'wg-rating--yellow-orange',
15
+ black: 'wg-rating--black',
16
+ };
17
+
18
+ /**
19
+ * @param {{ value?: number | string, size?: string, color?: string, showValue?: boolean, testId?: string }} props
20
+ */
21
+ export function renderUiRating(props = {}) {
22
+ const size = SIZE_CLASS[props.size] ? props.size : 'md';
23
+ const color = COLOR_CLASS[props.color] ? props.color : 'blue';
24
+ const raw = props.value ?? 0;
25
+ const hasPlus = String(raw).includes('+');
26
+ const numeric = hasPlus ? 5 : Math.max(0, Math.min(5, Number(raw) || 0));
27
+ const filled = hasPlus ? 5 : Math.round(numeric);
28
+ const stars = Array.from({ length: 5 }, (_, index) =>
29
+ `<span class="wg-rating__star${index < filled ? ' is-filled' : ''}" aria-hidden="true">★</span>`,
30
+ ).join('');
31
+ const valueHtml = props.showValue
32
+ ? `<span class="wg-rating__value">${escapeHtml(hasPlus ? '5+' : String(numeric))}</span>`
33
+ : '';
34
+ const testId = props.testId ?? 'ui-rating';
35
+ return `<div class="wg-rating ${SIZE_CLASS[size]} ${COLOR_CLASS[color]}" data-testid="${escapeHtmlAttr(testId)}" role="img" aria-label="Rating ${escapeHtmlAttr(String(raw))}">${valueHtml}<span class="wg-rating__stars">${stars}</span></div>`;
36
+ }
37
+
38
+ export const UI_RATING_CSS = `
39
+ .wg-rating { display: inline-flex; align-items: center; gap: var(--ui-rating-gap, 0.375rem); }
40
+ .wg-rating__stars { display: inline-flex; gap: 2px; color: rgb(var(--brand-border-rgb, 80 80 80)); }
41
+ .wg-rating__star.is-filled { color: rgb(var(--ui-rating-active-rgb, 0 102 255)); }
42
+ .wg-rating--yellow-orange .wg-rating__star.is-filled { color: rgb(245 158 11); }
43
+ .wg-rating--black .wg-rating__star.is-filled { color: rgb(0 0 0); }
44
+ .wg-rating__value { font-size: var(--ui-rating-value-font-size, 0.875rem); margin-right: 4px; }
45
+ .wg-rating--sm .wg-rating__stars { font-size: 0.875rem; }
46
+ .wg-rating--md .wg-rating__stars { font-size: 1.125rem; }
47
+ .wg-rating--lg .wg-rating__stars { font-size: 1.75rem; }
48
+ `;
@@ -0,0 +1,70 @@
1
+ import { renderUiButton } from '../atoms/button.mjs';
2
+ import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
3
+
4
+ /**
5
+ * @param {{ tabs: Array<{ id: string, label: string, labelHtml?: string, selected?: boolean, count?: number }>, size?: string, testId?: string, ariaLabel?: string, className?: string }} props
6
+ */
7
+ export function renderUiTabsGroup(props = {}) {
8
+ const tabs = props.tabs ?? [];
9
+ const sizeClass = props.size === 'sm' ? 'wg-tabs--sm' : props.size === 'lg' ? 'wg-tabs--lg' : 'wg-tabs--md';
10
+ const groupClass = ['wg-tabs', sizeClass, props.className ?? ''].filter(Boolean).join(' ');
11
+ const testId = props.testId ?? 'ui-tabs-group';
12
+ const triggers = tabs.map((tab) => renderUiTabsTrigger({
13
+ id: tab.id,
14
+ label: tab.label,
15
+ labelHtml: tab.labelHtml,
16
+ selected: tab.selected,
17
+ count: tab.count,
18
+ elementId: tab.elementId,
19
+ countId: tab.countId,
20
+ dataAttrKey: tab.dataAttrKey,
21
+ testId: tab.testId,
22
+ })).join('');
23
+ return `<div class="${escapeHtmlAttr(groupClass)}" role="tablist"${props.ariaLabel ? ` aria-label="${escapeHtmlAttr(props.ariaLabel)}"` : ''} data-testid="${escapeHtmlAttr(testId)}">${triggers}</div>`;
24
+ }
25
+
26
+ /**
27
+ * @param {{
28
+ * id: string,
29
+ * label?: string,
30
+ * labelHtml?: string,
31
+ * selected?: boolean,
32
+ * count?: number | string,
33
+ * testId?: string,
34
+ * elementId?: string,
35
+ * countId?: string,
36
+ * dataAttrKey?: string,
37
+ * }} props
38
+ */
39
+ export function renderUiTabsTrigger(props = {}) {
40
+ const selected = props.selected === true;
41
+ const dataKey = props.dataAttrKey ?? 'data-tab';
42
+ const countHtml = props.countId != null
43
+ ? `<span id="${escapeHtmlAttr(props.countId)}" class="count">${escapeHtml(String(props.count ?? 0))}</span>`
44
+ : props.count != null
45
+ ? `<span class="count">${escapeHtml(String(props.count))}</span>`
46
+ : '';
47
+ const labelHtml = props.labelHtml ?? (
48
+ countHtml
49
+ ? `${escapeHtml(props.label ?? '')} ${countHtml}`
50
+ : escapeHtml(props.label ?? '')
51
+ );
52
+ return renderUiButton({
53
+ unstyled: true,
54
+ id: props.elementId,
55
+ className: `wg-tabs__trigger workflow-subtab board-tab${selected ? ' is-active' : ''}`,
56
+ labelHtml,
57
+ testId: props.testId ?? `ui-tabs-trigger-${props.id}`,
58
+ role: 'tab',
59
+ ariaSelected: selected ? 'true' : 'false',
60
+ attrs: {
61
+ [dataKey]: props.id,
62
+ },
63
+ });
64
+ }
65
+
66
+ export const UI_TABS_CSS = `
67
+ .wg-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
68
+ .wg-tabs__trigger { border-bottom: 2px solid transparent; border-radius: 0; }
69
+ .wg-tabs__trigger.is-active { border-bottom-color: rgb(var(--ui-accent-rgb, 0 102 255)); color: rgb(var(--ui-accent-rgb, 0 102 255)); font-weight: 600; }
70
+ `;
@@ -0,0 +1 @@
1
+ export { renderUiModal, UI_MODAL_CSS } from '../atoms/modal.mjs';
@@ -0,0 +1,147 @@
1
+ import { renderUiBadge, UI_BADGE_CSS } from '../atoms/badge.mjs';
2
+ import { renderUiButton, UI_BUTTON_CSS } from '../atoms/button.mjs';
3
+ import { renderUiIcon, UI_ICON_CSS } from '../atoms/icon.mjs';
4
+ import { renderUiTextInput, UI_INPUT_CSS } from '../atoms/input.mjs';
5
+ import { renderUiSelect, UI_SELECT_CSS } from '../atoms/select.mjs';
6
+ import { renderUiModal, UI_MODAL_CSS } from '../atoms/modal.mjs';
7
+ import { renderUiRating, UI_RATING_CSS } from '../molecules/rating.mjs';
8
+ import { renderUiTabsGroup, UI_TABS_CSS } from '../molecules/tabs.mjs';
9
+ import { escapeHtml } from '../htmlEscape.mjs';
10
+
11
+ export const UI_ATOM_REGISTRY = [
12
+ {
13
+ id: 'button',
14
+ layer: 'atom',
15
+ title: 'Button',
16
+ renderPreview: () => [
17
+ renderUiButton({ label: 'Primary', variant: 'primary', testId: 'ui-kit-button-primary' }),
18
+ renderUiButton({ label: 'Secondary', variant: 'secondary', testId: 'ui-kit-button-secondary' }),
19
+ renderUiButton({ label: 'Flat', variant: 'flat', testId: 'ui-kit-button-flat' }),
20
+ ].join(' '),
21
+ },
22
+ {
23
+ id: 'badge',
24
+ layer: 'atom',
25
+ title: 'Badge',
26
+ renderPreview: () => [
27
+ renderUiBadge({ label: 'default', testId: 'ui-kit-badge-default' }),
28
+ renderUiBadge({ label: 'accent', tone: 'accent', testId: 'ui-kit-badge-accent' }),
29
+ renderUiBadge({ label: 'danger', tone: 'danger', testId: 'ui-kit-badge-danger' }),
30
+ ].join(' '),
31
+ },
32
+ {
33
+ id: 'select',
34
+ layer: 'atom',
35
+ title: 'Select',
36
+ renderPreview: () => renderUiSelect({
37
+ testId: 'ui-kit-select',
38
+ ariaLabel: 'Demo select',
39
+ options: [
40
+ { value: 'a', label: 'Option A', selected: true },
41
+ { value: 'b', label: 'Option B' },
42
+ ],
43
+ }),
44
+ },
45
+ {
46
+ id: 'text-input',
47
+ layer: 'atom',
48
+ title: 'Text input',
49
+ renderPreview: () => renderUiTextInput({ placeholder: 'Placeholder', testId: 'ui-kit-input' }),
50
+ },
51
+ {
52
+ id: 'icon',
53
+ layer: 'atom',
54
+ title: 'Icon',
55
+ renderPreview: () => renderUiIcon({ name: 'dot', testId: 'ui-kit-icon' }),
56
+ },
57
+ {
58
+ id: 'modal',
59
+ layer: 'atom',
60
+ title: 'Modal',
61
+ renderPreview: () => renderUiModal({
62
+ title: 'Preview modal',
63
+ bodyHtml: '<p class="muted">Static preview for /dev/ui-kit</p>',
64
+ testId: 'ui-kit-modal',
65
+ }),
66
+ },
67
+ ];
68
+
69
+ export const UI_MOLECULE_REGISTRY = [
70
+ {
71
+ id: 'rating',
72
+ layer: 'molecule',
73
+ title: 'Rating',
74
+ renderPreview: () => [
75
+ renderUiRating({ value: 4, size: 'md', showValue: true, testId: 'ui-kit-rating-md' }),
76
+ renderUiRating({ value: '5+', size: 'sm', color: 'yellow-orange', testId: 'ui-kit-rating-plus' }),
77
+ ].join(' '),
78
+ },
79
+ {
80
+ id: 'tabs',
81
+ layer: 'molecule',
82
+ title: 'Tabs',
83
+ renderPreview: () => renderUiTabsGroup({
84
+ testId: 'ui-kit-tabs',
85
+ ariaLabel: 'Demo tabs',
86
+ tabs: [
87
+ { id: 'drafts', label: 'Черновики', selected: true, count: 3 },
88
+ { id: 'archive', label: 'Архив', count: 12 },
89
+ ],
90
+ }),
91
+ },
92
+ ];
93
+
94
+ export const UI_COMPONENT_REGISTRY = [...UI_ATOM_REGISTRY, ...UI_MOLECULE_REGISTRY];
95
+
96
+ export function renderUiKitComponentCss() {
97
+ return [UI_BUTTON_CSS, UI_BADGE_CSS, UI_SELECT_CSS, UI_INPUT_CSS, UI_ICON_CSS, UI_MODAL_CSS, UI_RATING_CSS, UI_TABS_CSS].join('\n');
98
+ }
99
+
100
+ export function renderUiKitPageHtml() {
101
+ const sidebar = UI_COMPONENT_REGISTRY.map((entry) =>
102
+ `<li><a href="#ui-kit-${escapeHtml(entry.id)}">${escapeHtml(entry.title)}</a></li>`,
103
+ ).join('');
104
+
105
+ const sections = UI_COMPONENT_REGISTRY.map((entry) =>
106
+ `<section id="ui-kit-${escapeHtml(entry.id)}" class="ui-kit-section" data-testid="ui-kit-section-${escapeHtml(entry.id)}">` +
107
+ `<h2>${escapeHtml(entry.title)} <code>${escapeHtml(entry.id)}</code></h2>` +
108
+ `<div class="ui-kit-preview">${entry.renderPreview()}</div>` +
109
+ '</section>',
110
+ ).join('');
111
+
112
+ return `<!doctype html>
113
+ <html lang="ru" data-theme="dark" data-iohasc-theme="workgraph-dark">
114
+ <head>
115
+ <meta charset="utf-8">
116
+ <meta name="viewport" content="width=device-width, initial-scale=1">
117
+ <title>Work Graph UI Kit</title>
118
+ <link rel="stylesheet" href="/assets/fonts/GraphikLCG/stylesheet.css">
119
+ <link rel="stylesheet" href="/assets/design-tokens-workgraph-dark.css">
120
+ <style>
121
+ html {
122
+ font-family: var(--brand-font-sans);
123
+ letter-spacing: 0.01em;
124
+ -webkit-font-smoothing: antialiased;
125
+ -moz-osx-font-smoothing: grayscale;
126
+ text-rendering: optimizeLegibility;
127
+ }
128
+ body { margin: 0; background: rgb(var(--brand-bg-rgb, 30 30 30)); color: rgb(var(--ui-text-rgb, 212 212 212)); font: var(--text-base)/var(--text-base-line-height) var(--brand-font-sans); }
129
+ .ui-kit-layout { display: grid; grid-template-columns: 220px 1fr; min-height: 100vh; }
130
+ .ui-kit-sidebar { border-right: 1px solid rgb(var(--brand-border-rgb, 60 60 60)); padding: 16px; }
131
+ .ui-kit-sidebar ul { list-style: none; margin: 0; padding: 0; }
132
+ .ui-kit-sidebar a { color: rgb(var(--ui-link-rgb, 0 102 255)); text-decoration: none; display: block; padding: 6px 0; }
133
+ .ui-kit-main { padding: 20px; }
134
+ .ui-kit-section { margin-bottom: 28px; padding-bottom: 20px; border-bottom: 1px solid rgb(var(--brand-border-rgb, 60 60 60)); }
135
+ .ui-kit-preview { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 10px; }
136
+ .muted { color: rgb(var(--ui-muted-rgb, 157 157 157)); }
137
+ ${renderUiKitComponentCss()}
138
+ </style>
139
+ </head>
140
+ <body data-testid="ui-kit-root">
141
+ <div class="ui-kit-layout">
142
+ <aside class="ui-kit-sidebar"><h1>UI Kit</h1><ul>${sidebar}</ul><p class="muted"><a href="/">← Backlog UI</a></p></aside>
143
+ <main class="ui-kit-main">${sections}</main>
144
+ </div>
145
+ </body>
146
+ </html>`;
147
+ }
@@ -0,0 +1,36 @@
1
+ /** @typedef {'default' | 'accent' | 'muted' | 'danger' | 'ok'} BadgeTone */
2
+
3
+ const STATUS_LABELS = {
4
+ backlog: 'Бэклог',
5
+ ready: 'Доступно агенту',
6
+ claimed: 'Взято',
7
+ doing: 'В работе',
8
+ in_progress: 'В работе',
9
+ verify: 'Проверка',
10
+ blocked: 'Блокер',
11
+ done: 'Завершено',
12
+ verified: 'Проверено',
13
+ };
14
+
15
+ /**
16
+ * @param {string | null | undefined} status
17
+ */
18
+ export function statusLabel(status) {
19
+ const key = String(status ?? '').toLowerCase();
20
+ return STATUS_LABELS[key] ?? String(status ?? '');
21
+ }
22
+
23
+ /**
24
+ * @param {string | null | undefined} status
25
+ * @param {string | null | undefined} [queueKind]
26
+ * @returns {BadgeTone}
27
+ */
28
+ export function statusToBadgeTone(status, queueKind = null) {
29
+ if (queueKind === 'planned') return 'accent';
30
+ const key = String(status ?? '').toLowerCase();
31
+ if (key === 'ready' || key === 'claimed' || key === 'doing' || key === 'in_progress') return 'accent';
32
+ if (key === 'done' || key === 'verified') return 'ok';
33
+ if (key === 'blocked') return 'danger';
34
+ if (key === 'verify') return 'muted';
35
+ return 'default';
36
+ }