@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,275 @@
1
+ import {
2
+ CYCLE_SLICE_SCHEMA,
3
+ DEFAULT_DONE_ARCHIVE_CAP,
4
+ DONE_STATUSES,
5
+ OPERATIONAL_STATUSES,
6
+ deriveWorkCycle,
7
+ } from './workGraphCycleSlice.mjs';
8
+
9
+ export const HOME_SNAPSHOT_SCHEMA = 'home.snapshot.v1';
10
+
11
+ export const HOME_REFRESH_BUDGETS = Object.freeze({
12
+ kpiMs: 30000,
13
+ activeRunsMs: 5000,
14
+ myQueueMs: 30000,
15
+ inboxPreviewMs: 30000,
16
+ });
17
+
18
+ const ACTIVE_RUN_STATUSES = new Set(['claimed', 'doing', 'in_progress', 'verify']);
19
+ const READY_STATUS = 'ready';
20
+ const BLOCKED_STATUS = 'blocked';
21
+ const DEFAULT_QUEUE_LIMIT = 5;
22
+ const DEFAULT_INBOX_PREVIEW_LIMIT = 5;
23
+ const PRIORITY_RANK = { high: 0, medium: 1, low: 2 };
24
+ const RISK_RANK = { high: 0, medium: 1, low: 2 };
25
+
26
+ const compareText = (left, right) =>
27
+ String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
28
+
29
+ function rankOf(map, value, fallback = 1) {
30
+ if (value === undefined || value === null) {
31
+ return fallback;
32
+ }
33
+ const key = String(value).trim().toLowerCase();
34
+ return Object.hasOwn(map, key) ? map[key] : fallback;
35
+ }
36
+
37
+ function compareReadyItems(left, right) {
38
+ const priorityDelta = rankOf(PRIORITY_RANK, left.priority) - rankOf(PRIORITY_RANK, right.priority);
39
+ if (priorityDelta !== 0) {
40
+ return priorityDelta;
41
+ }
42
+ const riskDelta = rankOf(RISK_RANK, left.risk) - rankOf(RISK_RANK, right.risk);
43
+ if (riskDelta !== 0) {
44
+ return riskDelta;
45
+ }
46
+ return compareText(left.id, right.id);
47
+ }
48
+
49
+ function compareActiveRuns(left, right) {
50
+ const order = ['verify', 'doing', 'in_progress', 'claimed'];
51
+ const leftIndex = order.indexOf(left.status);
52
+ const rightIndex = order.indexOf(right.status);
53
+ if (leftIndex !== rightIndex) {
54
+ return leftIndex - rightIndex;
55
+ }
56
+ return compareText(left.id, right.id);
57
+ }
58
+
59
+ function summarizeQueueItem(item) {
60
+ return {
61
+ workId: item.id,
62
+ title: item.title ?? '',
63
+ status: item.status,
64
+ department: item.department ?? '',
65
+ ownerRole: item.ownerRole ?? '',
66
+ priority: item.priority ?? '',
67
+ risk: item.risk ?? '',
68
+ nextAction: item.nextAction ?? '',
69
+ parentId: item.labels?.['work.parent_id'] ?? item.parentId ?? null,
70
+ };
71
+ }
72
+
73
+ function summarizeActiveRun(item) {
74
+ return {
75
+ workId: item.id,
76
+ title: item.title ?? '',
77
+ status: item.status,
78
+ ownerRole: item.ownerRole ?? '',
79
+ department: item.department ?? '',
80
+ blockerReason: item.blocker?.reason ?? null,
81
+ };
82
+ }
83
+
84
+ function buildStatusBuckets(items) {
85
+ const counts = { ready: 0, blocked: 0, doing: 0, verify: 0, done: 0, backlog: 0, other: 0 };
86
+ for (const item of items) {
87
+ if (item.status === READY_STATUS) {
88
+ counts.ready += 1;
89
+ } else if (item.status === BLOCKED_STATUS) {
90
+ counts.blocked += 1;
91
+ } else if (item.status === 'verify') {
92
+ counts.verify += 1;
93
+ } else if (item.status === 'doing' || item.status === 'in_progress' || item.status === 'claimed') {
94
+ counts.doing += 1;
95
+ } else if (DONE_STATUSES.has(item.status)) {
96
+ counts.done += 1;
97
+ } else if (item.status === 'backlog') {
98
+ counts.backlog += 1;
99
+ } else {
100
+ counts.other += 1;
101
+ }
102
+ }
103
+ return counts;
104
+ }
105
+
106
+ function buildCycleProgress(items, options) {
107
+ const itemById = new Map(items.map((item) => [item.id, item]));
108
+ const currentCycle = options.currentCycle
109
+ ?? (typeof options.resolveCurrentCycle === 'function' ? options.resolveCurrentCycle(items) : null);
110
+
111
+ if (!currentCycle || currentCycle === 'all') {
112
+ const total = items.length;
113
+ const done = items.filter((item) => DONE_STATUSES.has(item.status)).length;
114
+ return {
115
+ cycleId: currentCycle ?? null,
116
+ total,
117
+ done,
118
+ operational: items.filter((item) => OPERATIONAL_STATUSES.has(item.status)).length,
119
+ percent: total === 0 ? 0 : Math.round((done / total) * 100),
120
+ };
121
+ }
122
+
123
+ const inCycle = items.filter((item) => deriveWorkCycle(item, itemById) === currentCycle);
124
+ const total = inCycle.length;
125
+ const done = inCycle.filter((item) => DONE_STATUSES.has(item.status)).length;
126
+ return {
127
+ cycleId: currentCycle,
128
+ total,
129
+ done,
130
+ operational: inCycle.filter((item) => OPERATIONAL_STATUSES.has(item.status)).length,
131
+ percent: total === 0 ? 0 : Math.round((done / total) * 100),
132
+ };
133
+ }
134
+
135
+ function pickMyQueue(items, options) {
136
+ const ownerRole = options.ownerRole ? String(options.ownerRole).trim().toLowerCase() : null;
137
+ const limit = Number.isInteger(options.queueLimit) && options.queueLimit > 0
138
+ ? options.queueLimit
139
+ : DEFAULT_QUEUE_LIMIT;
140
+
141
+ const readyItems = items.filter((item) => item.status === READY_STATUS);
142
+ const filtered = ownerRole
143
+ ? readyItems.filter((item) => String(item.ownerRole ?? '').trim().toLowerCase() === ownerRole)
144
+ : readyItems;
145
+
146
+ const sorted = [...filtered].sort(compareReadyItems);
147
+ return sorted.slice(0, limit).map(summarizeQueueItem);
148
+ }
149
+
150
+ function pickActiveRuns(items) {
151
+ return items
152
+ .filter((item) => ACTIVE_RUN_STATUSES.has(item.status))
153
+ .sort(compareActiveRuns)
154
+ .map(summarizeActiveRun);
155
+ }
156
+
157
+ function buildInboxPreview(events, limit = DEFAULT_INBOX_PREVIEW_LIMIT) {
158
+ if (!Array.isArray(events)) {
159
+ return [];
160
+ }
161
+ return events
162
+ .slice(0, Math.max(0, limit))
163
+ .map((event) => ({
164
+ id: event.id ?? '',
165
+ kind: event.kind ?? 'event',
166
+ severity: event.severity ?? 'info',
167
+ title: event.title ?? '',
168
+ summary: event.summary ?? '',
169
+ link: event.link ?? null,
170
+ at: event.at ?? null,
171
+ unread: event.unread !== false,
172
+ }));
173
+ }
174
+
175
+ function buildVerifyPassRate(options) {
176
+ if (typeof options.verifyPassRate === 'number' && Number.isFinite(options.verifyPassRate)) {
177
+ return {
178
+ rate: Math.max(0, Math.min(1, options.verifyPassRate)),
179
+ windowRuns: Number.isInteger(options.verifyWindowRuns) ? options.verifyWindowRuns : null,
180
+ source: options.verifyPassRateSource ?? 'caller',
181
+ };
182
+ }
183
+ return { rate: null, windowRuns: null, source: 'unavailable' };
184
+ }
185
+
186
+ function buildThroughput(options) {
187
+ if (typeof options.throughputPerDay === 'number' && Number.isFinite(options.throughputPerDay)) {
188
+ return {
189
+ perDay: options.throughputPerDay,
190
+ windowDays: Number.isInteger(options.throughputWindowDays) ? options.throughputWindowDays : 7,
191
+ source: options.throughputSource ?? 'caller',
192
+ };
193
+ }
194
+ return { perDay: null, windowDays: null, source: 'unavailable' };
195
+ }
196
+
197
+ function buildDaemonUptime(options) {
198
+ if (options.daemonStartedAt && Number.isFinite(Date.parse(options.daemonStartedAt))) {
199
+ return {
200
+ startedAt: options.daemonStartedAt,
201
+ uptimeMs: Math.max(0, Date.now() - Date.parse(options.daemonStartedAt)),
202
+ source: options.daemonSource ?? 'caller',
203
+ };
204
+ }
205
+ return { startedAt: null, uptimeMs: null, source: 'unavailable' };
206
+ }
207
+
208
+ function buildAgentRunsToday(options) {
209
+ if (Number.isInteger(options.agentRunsToday) && options.agentRunsToday >= 0) {
210
+ return { count: options.agentRunsToday, source: options.agentRunsSource ?? 'caller' };
211
+ }
212
+ return { count: null, source: 'unavailable' };
213
+ }
214
+
215
+ /**
216
+ * Build a Home (mission control) snapshot from a workgraph.snapshot.v1.
217
+ *
218
+ * @param {object} workGraphSnapshot — must contain {items: WorkItem[]}.
219
+ * @param {object} [options]
220
+ * @param {string} [options.ownerRole] — filter My queue by owner role.
221
+ * @param {number} [options.queueLimit=5] — My queue size cap.
222
+ * @param {number} [options.inboxPreviewLimit=5] — inbox preview cap.
223
+ * @param {string} [options.currentCycle] — explicit cycle id; otherwise resolveCurrentCycle().
224
+ * @param {(items: WorkItem[]) => string} [options.resolveCurrentCycle]
225
+ * @param {Array<object>} [options.inboxPreview] — pre-built inbox events (newest first).
226
+ * @param {number} [options.verifyPassRate]
227
+ * @param {number} [options.throughputPerDay]
228
+ * @param {string} [options.daemonStartedAt]
229
+ * @param {number} [options.agentRunsToday]
230
+ * @param {number} [options.doneArchiveCap=DEFAULT_DONE_ARCHIVE_CAP]
231
+ * @returns {object} schema home.snapshot.v1
232
+ */
233
+ export function buildHomeSnapshot(workGraphSnapshot, options = {}) {
234
+ if (!workGraphSnapshot || typeof workGraphSnapshot !== 'object') {
235
+ throw new TypeError('workGraphSnapshot must be an object');
236
+ }
237
+ const items = Array.isArray(workGraphSnapshot.items) ? workGraphSnapshot.items : [];
238
+
239
+ const statusBuckets = buildStatusBuckets(items);
240
+ const cycleProgress = buildCycleProgress(items, options);
241
+
242
+ return {
243
+ schema: HOME_SNAPSHOT_SCHEMA,
244
+ sourceSchema: workGraphSnapshot.schema ?? null,
245
+ cycleSliceSchema: CYCLE_SLICE_SCHEMA,
246
+ generatedAt: options.now ?? new Date().toISOString(),
247
+ refreshBudgets: HOME_REFRESH_BUDGETS,
248
+ kpi: {
249
+ cycleProgress,
250
+ ready: statusBuckets.ready,
251
+ blocked: statusBuckets.blocked,
252
+ verify: statusBuckets.verify,
253
+ doing: statusBuckets.doing,
254
+ done: statusBuckets.done,
255
+ backlog: statusBuckets.backlog,
256
+ verifyPassRate: buildVerifyPassRate(options),
257
+ throughput: buildThroughput(options),
258
+ daemonUptime: buildDaemonUptime(options),
259
+ agentRunsToday: buildAgentRunsToday(options),
260
+ },
261
+ myQueue: {
262
+ ownerRole: options.ownerRole ?? null,
263
+ items: pickMyQueue(items, options),
264
+ doneArchiveCap: options.doneArchiveCap ?? DEFAULT_DONE_ARCHIVE_CAP,
265
+ },
266
+ activeRuns: pickActiveRuns(items),
267
+ inboxPreview: {
268
+ limit: Number.isInteger(options.inboxPreviewLimit) && options.inboxPreviewLimit > 0
269
+ ? options.inboxPreviewLimit
270
+ : DEFAULT_INBOX_PREVIEW_LIMIT,
271
+ items: buildInboxPreview(options.inboxPreview, options.inboxPreviewLimit),
272
+ },
273
+ sessionEpicId: options.sessionEpicId ?? null,
274
+ };
275
+ }
@@ -0,0 +1,140 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ export const INBOX_EVENTS_SCHEMA = 'inbox.events.v1';
5
+ export const INBOX_READ_STATE_SCHEMA = 'inbox.read-state.v1';
6
+ export const DEFAULT_INBOX_READ_STATE_PATH = '.workgraph/inbox-read-state.json';
7
+
8
+ function parseTime(value) {
9
+ if (!value) {
10
+ return 0;
11
+ }
12
+ const parsed = Date.parse(value);
13
+ return Number.isFinite(parsed) ? parsed : 0;
14
+ }
15
+
16
+ export function buildInboxEventsFromSources(sources = {}) {
17
+ const events = [];
18
+
19
+ for (const run of sources.workerRuns ?? []) {
20
+ const at = run.recordedAt ?? run.startedAt ?? run.finishedAt ?? null;
21
+ events.push({
22
+ id: `agent-run:${run.runId ?? run.taskId ?? at ?? events.length}`,
23
+ kind: 'agent-run',
24
+ severity: run.ok === false || run.status === 'failed' ? 'error' : 'info',
25
+ title: run.taskId ? `Agent run: ${run.taskId}` : 'Agent run',
26
+ summary: run.failureReason ?? run.summary ?? run.transition ?? '',
27
+ link: run.taskId ? { type: 'work', workId: run.taskId } : null,
28
+ at,
29
+ unread: true,
30
+ });
31
+ }
32
+
33
+ for (const entry of sources.daemonAudit ?? []) {
34
+ const at = entry.recordedAt ?? entry.at ?? null;
35
+ events.push({
36
+ id: `daemon:${entry.tickId ?? entry.taskId ?? at ?? events.length}`,
37
+ kind: 'daemon',
38
+ severity: entry.event === 'recovery' || entry.status === 'failed' ? 'warning' : 'info',
39
+ title: entry.event ? `Daemon: ${entry.event}` : 'Daemon tick',
40
+ summary: entry.summary ?? entry.message ?? '',
41
+ link: entry.taskId ? { type: 'work', workId: entry.taskId } : null,
42
+ at,
43
+ unread: true,
44
+ });
45
+ }
46
+
47
+ for (const record of sources.analyticsRecords ?? []) {
48
+ const key = record.key ?? record.id ?? '';
49
+ events.push({
50
+ id: `analytics:${key || events.length}`,
51
+ kind: 'analytics',
52
+ severity: 'info',
53
+ title: record.title ?? key ?? 'Analytics',
54
+ summary: record.topic ?? record.query ?? '',
55
+ link: record.bodyPath ? { type: 'analytics', path: record.bodyPath, key } : null,
56
+ at: record.updatedAt ?? record.createdAt ?? record.appendedAt ?? null,
57
+ unread: true,
58
+ });
59
+ }
60
+
61
+ for (const item of sources.verifyItems ?? []) {
62
+ if (item.status !== 'verify') {
63
+ continue;
64
+ }
65
+ events.push({
66
+ id: `verify:${item.id}`,
67
+ kind: 'work-verify',
68
+ severity: 'info',
69
+ title: `Verify: ${item.title ?? item.id}`,
70
+ summary: item.nextAction ?? '',
71
+ link: { type: 'work', workId: item.id },
72
+ at: item.labels?.['work.decision.at'] ?? null,
73
+ unread: true,
74
+ });
75
+ }
76
+
77
+ return events.sort((left, right) => parseTime(right.at) - parseTime(left.at));
78
+ }
79
+
80
+ export function applyInboxReadState(events, readState = {}) {
81
+ const readIds = new Set(Array.isArray(readState.readIds) ? readState.readIds : []);
82
+ return events.map((event) => ({
83
+ ...event,
84
+ unread: !readIds.has(event.id),
85
+ }));
86
+ }
87
+
88
+ export function countUnreadInboxEvents(events) {
89
+ return events.filter((event) => event.unread !== false).length;
90
+ }
91
+
92
+ export async function readInboxReadState(options = {}) {
93
+ const cwd = options.cwd ?? process.cwd();
94
+ const statePath = join(cwd, options.statePath ?? DEFAULT_INBOX_READ_STATE_PATH);
95
+
96
+ try {
97
+ const text = await readFile(statePath, 'utf8');
98
+ const parsed = JSON.parse(text);
99
+ return {
100
+ schema: INBOX_READ_STATE_SCHEMA,
101
+ readIds: Array.isArray(parsed.readIds) ? parsed.readIds : [],
102
+ updatedAt: parsed.updatedAt ?? null,
103
+ };
104
+ } catch {
105
+ return { schema: INBOX_READ_STATE_SCHEMA, readIds: [], updatedAt: null };
106
+ }
107
+ }
108
+
109
+ export async function markInboxEventsRead(eventIds, options = {}) {
110
+ const cwd = options.cwd ?? process.cwd();
111
+ const statePath = join(cwd, options.statePath ?? DEFAULT_INBOX_READ_STATE_PATH);
112
+ const current = await readInboxReadState({ cwd, statePath });
113
+ const merged = new Set([...current.readIds, ...eventIds.filter(Boolean)]);
114
+
115
+ const next = {
116
+ schema: INBOX_READ_STATE_SCHEMA,
117
+ readIds: [...merged],
118
+ updatedAt: new Date().toISOString(),
119
+ };
120
+
121
+ await mkdir(join(cwd, '.workgraph'), { recursive: true });
122
+ await writeFile(statePath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
123
+ return next;
124
+ }
125
+
126
+ export async function readInboxEventsResponse(options = {}) {
127
+ const cwd = options.cwd ?? process.cwd();
128
+ const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 50;
129
+ const sources = options.sources ?? {};
130
+ const events = buildInboxEventsFromSources(sources);
131
+ const readState = await readInboxReadState({ cwd, statePath: options.statePath });
132
+ const withRead = applyInboxReadState(events, readState);
133
+
134
+ return {
135
+ schema: INBOX_EVENTS_SCHEMA,
136
+ generatedAt: new Date().toISOString(),
137
+ unreadCount: countUnreadInboxEvents(withRead),
138
+ items: withRead.slice(0, limit),
139
+ };
140
+ }
@@ -0,0 +1,245 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ import {
5
+ appendWorkItemAtomToBacklogText,
6
+ findWorkItemAtomSpan,
7
+ writeBacklogTextAtomically,
8
+ } from './workGraphBacklogPersist.mjs';
9
+ import {
10
+ appendWorkItemAtomToIntentTree,
11
+ readWorkItemAtomFromRepo,
12
+ } from './intentTreeWorkItems.mjs';
13
+ import { formatStepAtomDraft, validateStepAtomDraft } from './stepAtomFormatter.mjs';
14
+
15
+ export const INTENT_COMPOSER_PROPOSAL_SCHEMA = 'intent-composer.proposal.v1';
16
+ export const INTENT_COMPOSER_DRAFT_SCHEMA = 'intent-composer.draft.v1';
17
+ export const INTENT_COMPOSER_APPLY_SCHEMA = 'intent-composer.apply.response.v1';
18
+
19
+ function slugifyIntent(value) {
20
+ const asciiParts = String(value ?? '')
21
+ .toLowerCase()
22
+ .split(/[^a-z0-9]+/u)
23
+ .filter(Boolean);
24
+
25
+ if (asciiParts.length > 0) {
26
+ return asciiParts.join('-').slice(0, 40);
27
+ }
28
+
29
+ const hash = [...String(value ?? '')].reduce((acc, ch) => ((acc * 31) + ch.charCodeAt(0)) >>> 0, 7);
30
+ return `msg-${hash.toString(36)}`;
31
+ }
32
+
33
+ function atomNameFromSlug(slug) {
34
+ const safe = slug.replace(/-/g, '_');
35
+ return `Задача_${safe}`;
36
+ }
37
+
38
+ export function buildIntentDraftFromMessage(message, options = {}) {
39
+ const text = String(message ?? '').trim();
40
+ if (text === '') {
41
+ throw new TypeError('message is required');
42
+ }
43
+
44
+ const slug = slugifyIntent(text);
45
+ const workId = String(options.workId ?? `intent-${slug}`).trim();
46
+ const title = text.length > 120 ? `${text.slice(0, 117)}...` : text;
47
+
48
+ const draft = {
49
+ name: atomNameFromSlug(slug),
50
+ profile: 'work_item',
51
+ basis: [text],
52
+ vector: [
53
+ 'Оформить WorkItem из намерения пользователя через раздел «Замысел».',
54
+ ],
55
+ goal: [
56
+ 'Получить reviewable backlog draft без записи chat transcript в канон.',
57
+ ],
58
+ checks: [
59
+ 'draft проходит StepAtomDraft validation',
60
+ 'operator подтверждает apply в backlog',
61
+ ],
62
+ labels: {
63
+ 'atom.profile': 'work_item',
64
+ 'work.id': workId,
65
+ 'work.title': title,
66
+ 'work.status': 'backlog',
67
+ 'work.owner_role': options.ownerRole ?? 'product_architect',
68
+ 'work.department': options.department ?? 'ui-dashboard',
69
+ 'work.priority': options.priority ?? 'medium',
70
+ 'work.risk': 'low',
71
+ 'work.next_action': options.nextAction ?? 'review draft и promote в ready',
72
+ 'intake.source_kind': 'intent-composer',
73
+ 'intake.review_status': 'pending',
74
+ 'work.pipeline_stage': 'intake',
75
+ 'trace.status': 'pending',
76
+ 'migration.strategy': 'rebuild',
77
+ },
78
+ };
79
+
80
+ const questions = text.length < 48
81
+ ? ['Уточните ожидаемый результат и target files для WorkItem.']
82
+ : [];
83
+
84
+ return {
85
+ schema: INTENT_COMPOSER_DRAFT_SCHEMA,
86
+ message: text,
87
+ suggestedWorkId: workId,
88
+ reviewRequired: true,
89
+ questions,
90
+ risks: [],
91
+ targetFiles: [],
92
+ criteria: draft.checks,
93
+ draft,
94
+ };
95
+ }
96
+
97
+ export function buildIntentComposerProposal(body, options = {}) {
98
+ const message = String(body?.message ?? body?.text ?? '').trim();
99
+ if (message === '') {
100
+ return {
101
+ schema: INTENT_COMPOSER_PROPOSAL_SCHEMA,
102
+ ok: false,
103
+ error: 'message_required',
104
+ validationErrors: ['message is required'],
105
+ };
106
+ }
107
+
108
+ const intentDraft = buildIntentDraftFromMessage(message, {
109
+ workId: body.workId,
110
+ ownerRole: body.ownerRole,
111
+ department: body.department,
112
+ priority: body.priority,
113
+ nextAction: body.nextAction,
114
+ });
115
+
116
+ const validationErrors = validateStepAtomDraft(intentDraft.draft);
117
+ let formattedAtom = null;
118
+
119
+ if (validationErrors.length === 0) {
120
+ try {
121
+ formattedAtom = formatStepAtomDraft(intentDraft.draft);
122
+ } catch (error) {
123
+ validationErrors.push(error instanceof Error ? error.message : String(error));
124
+ }
125
+ }
126
+
127
+ return {
128
+ schema: INTENT_COMPOSER_PROPOSAL_SCHEMA,
129
+ ok: validationErrors.length === 0,
130
+ error: validationErrors.length === 0 ? null : 'validation_failed',
131
+ intentDraft,
132
+ formattedAtom,
133
+ validationErrors,
134
+ validationWarnings: options.warnings ?? [],
135
+ reviewRequired: true,
136
+ distinctFromAgentRun: true,
137
+ };
138
+ }
139
+
140
+ export function parseIntentComposerRequestBody(rawBody) {
141
+ if (rawBody === undefined || rawBody === null) {
142
+ return {};
143
+ }
144
+
145
+ if (typeof rawBody === 'string' && rawBody.trim() === '') {
146
+ return {};
147
+ }
148
+
149
+ const body = typeof rawBody === 'string' ? JSON.parse(rawBody) : rawBody;
150
+
151
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
152
+ throw new TypeError('intent composer body must be a JSON object');
153
+ }
154
+
155
+ return body;
156
+ }
157
+
158
+ export async function executeIntentComposerProposal(options = {}) {
159
+ const body = parseIntentComposerRequestBody(options.body ?? {});
160
+ return buildIntentComposerProposal(body, options);
161
+ }
162
+
163
+ export async function executeIntentComposerApply(options = {}) {
164
+ const cwd = options.cwd ?? process.cwd();
165
+ const body = parseIntentComposerRequestBody(options.body ?? {});
166
+
167
+ const proposal = body.proposal
168
+ ?? buildIntentComposerProposal(body);
169
+
170
+ if (!proposal.ok || !proposal.intentDraft?.draft) {
171
+ return {
172
+ schema: INTENT_COMPOSER_APPLY_SCHEMA,
173
+ ok: false,
174
+ error: proposal.error ?? 'invalid_proposal',
175
+ workId: null,
176
+ persistedBacklog: false,
177
+ validationErrors: proposal.validationErrors ?? [],
178
+ };
179
+ }
180
+
181
+ const workId = String(proposal.intentDraft.suggestedWorkId ?? '').trim();
182
+ const formattedAtom = proposal.formattedAtom
183
+ ?? formatStepAtomDraft(proposal.intentDraft.draft);
184
+
185
+ if (options.backlogText !== undefined || options.backlogPath) {
186
+ const backlogPath = resolve(cwd, options.backlogPath);
187
+ const backlogText = options.backlogText ?? await readFile(backlogPath, 'utf8');
188
+ if (findWorkItemAtomSpan(backlogText, workId)) {
189
+ return {
190
+ schema: INTENT_COMPOSER_APPLY_SCHEMA,
191
+ ok: false,
192
+ error: 'duplicate_work_id',
193
+ workId,
194
+ persistedBacklog: false,
195
+ validationErrors: [`work.id already exists: ${workId}`],
196
+ };
197
+ }
198
+
199
+ const nextText = appendWorkItemAtomToBacklogText(backlogText, formattedAtom);
200
+
201
+ if (options.persistBacklog !== false) {
202
+ await writeBacklogTextAtomically(backlogPath, nextText);
203
+ }
204
+
205
+ return {
206
+ schema: INTENT_COMPOSER_APPLY_SCHEMA,
207
+ ok: true,
208
+ error: null,
209
+ workId,
210
+ persistedBacklog: options.persistBacklog !== false,
211
+ validationErrors: [],
212
+ formattedAtom,
213
+ };
214
+ }
215
+
216
+ try {
217
+ await readWorkItemAtomFromRepo(workId, { ...options, cwd });
218
+ return {
219
+ schema: INTENT_COMPOSER_APPLY_SCHEMA,
220
+ ok: false,
221
+ error: 'duplicate_work_id',
222
+ workId,
223
+ persistedBacklog: false,
224
+ validationErrors: [`work.id already exists: ${workId}`],
225
+ };
226
+ } catch (error) {
227
+ if (!String(error instanceof Error ? error.message : error).includes('not found')) {
228
+ throw error;
229
+ }
230
+ }
231
+
232
+ if (options.persistBacklog !== false) {
233
+ await appendWorkItemAtomToIntentTree(formattedAtom, { ...options, cwd });
234
+ }
235
+
236
+ return {
237
+ schema: INTENT_COMPOSER_APPLY_SCHEMA,
238
+ ok: true,
239
+ error: null,
240
+ workId,
241
+ persistedBacklog: options.persistBacklog !== false,
242
+ validationErrors: [],
243
+ formattedAtom,
244
+ };
245
+ }